Builders of a template

2

A design pattern not very well known is the PassKey pattern that is used mainly to restrict access to certain public functions (the option would be to use friend in the main classes and that produces too much coupling).

A basic implementation could be:

template<class T>
class PassKey
{
  friend T;

  PassKey()
  { }

  PassKey(PassKey const&)
  { }

  PassKey& operator=(PassKey const&) = delete;
};

Note that all the elements are private ... the grace is that only the type T will be able to create objects of this type. An example of use to see it better:

class Objeto
{
public:

  // Puede ser llamada por cualquiera
  void FuncionPublica()
  { }

  // Solo Autorizado puede llamar a esta funcion
  void FuncionRestringida(PassKey<Autorizado>)
  { }

private:

  // Nadie puede llamar a esta funcion
  void FuncionPrivada()
  { }
};

class Autorizado
{
public:

  void Func(Objeto& obj)
  {
    obj.FuncionPublica(); // ok
    obj.FuncionRestringida(PassKey<Autorizado>()); // ok
    obj.FuncionPrivada(); // error de compilacion -> esperado
  }
};


class Espia
{
public:

  void Func(Objeto& obj)
  {
    obj.FuncionPublica(); // ok
    obj.FuncionRestringida(PassKey<Autorizado>()); // error de compilacion
    obj.FuncionRestringida(PassKey<Espia>()); // error de compilacion
    obj.FuncionPrivada(); // error de compilacion -> esperado
  }
};

Well, now that the context is explained, we go to the mess. Now it turns out that Objeto.FuncionRestringida() has to be accessed by two different classes:

class Objeto
{
public:

  // Solo Autorizado y OtraClase pueden llamar a esta funcion
  void FuncionRestringida(PassKey<Autorizado,OtraClase>);

};

... in theory the problem does not have too much difficulty ... just enlarge the template. let's see:

version1

The template now supports two types:

template<class T1, class T2 = void>
class PassKey
{
  friend T1;
  friend T2;

  PassKey()
  { }

  PassKey(PassKey const&)
  { }

  PassKey& operator=(PassKey const&) = delete;
};

Problem ... the copy constructor requires that the received object be of type PassKey<Autorizado,OtraClase> , which forces to modify both Autorizado and OtraClase so that they create the object of the correct type ... too cumbersome:

class Autorizado
{
public:

  void Func(Objeto& obj)
  {
    obj.FuncionRestringida(PassKey<Autorizado,OtraClase>()); // ok pero engorroso..
    obj.FuncionRestringida(PassKey<OtraClase,Autorizado>()); // error de compilacion
  }
};

The idea should be that each one worries about creating a key with its type and creating some mechanism that makes the relevant conversions:

PassKey<T1> -> PassKey<T1,T2>
PassKey<T2> -> PassKey<T1,T2>

version 2

We tried to overload the builders to make the conversions:

template<class T1, class T2 = void>
class PassKey
{
  friend T1;
  friend T2;

  PassKey()
  { }

  PassKey(PassKey const&)
  { }

  PassKey(PassKey<T1> const&)
  { }

  PassKey(PassKey<T2> const&)
  { }

  PassKey& operator=(PassKey const&) = delete;
};

Problem ... compilation errors occur:

error: multiple overloads of 'PassKey' instantiate to the same signature 'void (const PassKey<Autorizado> &)'
  PassKey(PassKey<T1> const&)
  ^
note: in instantiation of template class 'PassKey<Autorizado, void>' requested here
    obj.FuncionRestringida(PassKey<Autorizado>());
                       ^
note: previous declaration is here
  PassKey(PassKey const&)
  ^
error: multiple overloads of 'PassKey' instantiate to the same signature 'void (const PassKey<OtraClase> &)'
  PassKey(PassKey<T1> const&)
  ^
note: in instantiation of template class 'PassKey<OtraClase, void>' requested here
    obj.FuncionRestringida(PassKey<OtraClase>());
                       ^
note: previous declaration is here
  PassKey(PassKey const&)
error: 'PassKey<T1, T2>::PassKey(const PassKey<T2>&) [with T1 = void; T2 = void]' cannot be overloaded
error: with 'PassKey<T1, T2>::PassKey(const PassKey<T1>&) [with T1 = void; T2 = void]'
In member function 'void Autorizado::Func(Objeto&)':
error:   initializing argument 1 of 'void Objeto::FuncionRestringida(PassKey<Autorizado>)'

At this point ... is there a solution to the problem?

    
asked by eferion 23.06.2017 в 11:36
source

1 answer

1

The first problem in this case is that there is a possible duplicate function:

template<class T1, class T2 = void>
class PassKey
{
  friend T1;
  friend T2;

  PassKey(PassKey const&) // 1
  { }

  PassKey(PassKey<T1> const&) // 2
  { }
};

What happens when this line is executed?:

class Autorizado
{
  // ...
  obj.FuncionRestringida(PassKey<Autorizado>());
  // ...
};

An attempt is made to instantiate an object of type PassKey<Autorizado,void> , which results in the following interface:

class PassKey<Autorizado,void>
{
  PassKey(PassKey<Autorizado,void> const&) // 1
  { }

  PassKey(PassKey<Autorizado,void> const&) // 2
  { }
};

The copy constructor produces a duplicity that is not supported by the compiler. It is not easy to disable the copy constructor based on T1 and T2 ... and given the purpose of this object (use and throw key) the most sensible thing to do is to delete the copy constructor:

template<class T1, class T2 = void>
class PassKey
{
  friend T1;
  friend T2;

  PassKey()
  { }

  PassKey(PassKey<T1> const&)
  { }

  PassKey(PassKey<T2> const&)
  { }

  PassKey& operator=(PassKey const&) = delete;
};

Now, depending on the chosen compiler, the solution can compile or not. Some compilers have problems when solving the problem mentioned in the question ...

  

error: 'PassKey :: PassKey (const PassKey &) [with T1 = void; T2 = void] 'can not be overloaded

Where does that T1 = void; T2 = void come from?

Maybe it's not something that is seen at first glance but there it is. The following object:

PassKey<Autorizado>();

It has the following interface:

class PassKey<Autorizado>
{
  PassKey()
  { }

  PassKey(PassKey<Autorizado> const&)
  { }

  PassKey(PassKey<void> const&)
  { }

  PassKey& operator=(PassKey const&) = delete;
};

If we expand the templates by both types explicitly:

class PassKey<Autorizado,void>
{
  PassKey()
  { }

  PassKey(PassKey<Autorizado,void> const&)
  { }

  PassKey(PassKey<void,void> const&) // <<--- AQUI!!!
  { }

  PassKey& operator=(PassKey const&) = delete;
};

And voila, we have already found the implementation T1 = void; T2 = void , but ... Where is the problem?

If we review the implementation of the <void,void> specialization, we have the following:

class PassKey<void,void>
{
  PassKey(PassKey<void,void> const&) // 1
  { }

  PassKey(PassKey<void,void> const&) // 2
  { }
};

Wait ... where do these two builders come from if we had already corrected this problem at the beginning?

Let's see our current implementation of the template:

template<class T1, class T2 = void>
class PassKey
{
  friend T1;
  friend T2;

  PassKey()
  { }

  PassKey(PassKey<T1> const&) // 1
  { }

  PassKey(PassKey<T2> const&) // 2
  { }

  PassKey& operator=(PassKey const&) = delete;
};

The error comes from the two commented builders. Being T1 = T2 both constructors become equal. This problem is solved by creating a specialization for <void,void> that eliminates this ambiguity:

template<>
class PassKey<void,void>
{
  PassKey();

  PassKey(PassKey const&);

  PassKey& operator=(PassKey const&) = delete;
};

Although we can also choose to create a slightly more generic solution:

template<class T>
class PassKey<T,T>
{
  friend class T;

  PassKey();

  PassKey(PassKey const&);

  PassKey& operator=(PassKey const&) = delete;
};

And with this we already managed to solve all the problems of a solution as apparently simple as the one posed in the question.

    
answered by 27.06.2017 / 12:05
source