Подтвердить что ты не робот

С++ Как указать всех друзей шаблона с аргументом по умолчанию?

Чтобы определить друга шаблонного класса с аргументом по умолчанию, вам нужно указать всех друзей, как в приведенном ниже коде (который работает)?

// Different class implementations
enum ClassImplType { CIT_CHECK, CIT_FAST, CIT_GPU, CIT_SSE, CIT_NOF_TYPES } ;

// Graph class has default template argument CIT_CHECK
template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph {
  //...
};

// Vertex class
template <typename T>
class vertex {
  //...
  friend class graph<T, CIT_CHECK>;
  friend class graph<T, CIT_FAST>;
  friend class graph<T, CIT_GPU>;
  friend class graph<T, CIT_SSE>;
};

Я могу себе представить, что существует более короткий способ обозначить, что друг определен для всех возможных значений перечисления ClassImplType. Что-то вроде friend class graph<T, ClassImplType>, но последнее не работает, конечно.

Извините, если используемая терминология неверна.

4b9b3361

Ответ 1

Я могу представить, что существует более короткий способ обозначить, что друг определен для всех возможных значений перечисления ClassImplType.

К сожалению, на самом деле этого не происходит. Вы можете попробовать с помощью

template<ClassImplType I> friend class graph<T, I>;

но стандарт просто запрещает подделывать частичные специализации:

§14.5.4 [temp.friend] p8

Декларации друзей не должны объявлять частичную специализацию. [Пример:

template<class T> class A { };
class X {
  template<class T> friend class A<T*>; // error
};

-end пример]

Вы можете только подружиться со всеми:

template<class U, ClassImplType I>
friend class graph;

Или конкретный:

friend class graph<T /*, optional-second-arg*/>;

Я не могу понять, как подружиться со всеми возможными специализациями может быть проблема, если честно, но позвольте предположить, что это так. Один из обходных путей, который я знаю, будет использовать шаблон ключа доступа, хотя мы будем использовать слегка вырезанную версию (мы не можем использовать механизм allow здесь, поскольку он не работает хорошо для обеспечения доступа ко всем специализациям шаблона):

template<class T>
class passkey{    
  passkey(){}
  friend T;

  // optional
  //passkey(passkey const&) = delete;
  //passkey(passkey&&) = delete;
};

// Different class implementations
enum ClassImplType { CIT_CHECK, CIT_FAST, CIT_GPU, CIT_SSE, CIT_NOF_TYPES } ;

template<class> struct vertex;

// Graph class has default template argument CIT_CHECK
template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph {
public:
  void call_f(vertex<T>& v){ v.f(passkey<graph>()); }
  //...
};

// Vertex class
template <typename T>
class vertex {
  //...
public:
  template<ClassImplType I>
  void f(passkey<graph<T,I>>){}
};

Живой пример с тестами.

Вы заметите, что вам нужно сделать все функциональные возможности, которые graph должен получить доступ к общедоступным, но это не проблема из-за ключей доступа, которые могут быть созданы только с помощью указанных graph специализаций.

Вы также можете пойти дальше и создать прокси-класс, который можно использовать для доступа к функциональности вершин (только изменения graph):

// Graph class has default template argument CIT_CHECK
template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph{
  typedef passkey<graph> key;
  // proxy for succinct multiple operations
  struct vertex_access{
    vertex_access(vertex<T>& v, key k)
      : _v(v), _key(k){}

    void f(){ _v.f(_key); }

  private:
    vertex<T>& _v;
    key _key;
  };

public:
  void call_f(vertex<T>& v){
    vertex_access va(v, key());
    va.f(); va.f(); va.f();
    // or
    v.f(key());
  }
  //...
};

Пример в реальном времени.

Ответ 2

Вы можете создать шаблон friend:

template<typename U, ClassImplType  V>
friend class graph_foo;

Я пытаюсь выяснить, как сохранить T - я обновлю, если узнаю.

Ответ 3

Я подумал о следующем: "Исправить это", используя рекурсивное наследование.

Встроенные комментарии объясняют, что происходит:

#include <type_traits>
#include <string>
#include <iostream>
#include <typeinfo>

// Different class implementations
enum ClassImplType { CIT_CHECK, CIT_FAST, CIT_GPU, CIT_SSE, CIT_NOF_TYPES };

template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph;

// Vertex class
namespace impl
{
    template <typename, ClassImplType, typename enabler = void> struct vertex_impl;

    ///////////////////////////////////////////////////////////////
    // actual implementation (stop condition of recursion)
    static const ClassImplType CIT_ENDMARKER = (ClassImplType) -1;

    template <typename T> struct vertex_impl<T, CIT_ENDMARKER> 
    {
        protected: // make it protected rather than private
            int secret() const { return 42; }
    };

    ///////////////////////////////////////////////////////////////
    // recursion, just to mark friends
    template <typename T, ClassImplType impl_type>
        struct vertex_impl<T, impl_type, typename std::enable_if<CIT_ENDMARKER != impl_type>::type>
            : public vertex_impl<T, ClassImplType(impl_type - 1)>
        {
             friend class ::graph<T, impl_type>;
        };
}

///////////////////////////////////////////////////////////////
// Public typedef
#if 1
    template <typename T> struct vertex : impl::vertex_impl<T, CIT_NOF_TYPES> { };
#else // or c++11
    template <typename T> using vertex = impl::vertex_impl<T, CIT_NOF_TYPES>;
#endif

template <typename T, ClassImplType impl_type>
class graph
{
    public:
        static void TestFriendOf(const vertex<T>& byref)
        {
            std::cout << byref.secret() << std::endl;
        }
};

int main(int argc, const char *argv[])
{
    vertex<int> t;

    graph<int, CIT_CHECK>     :: TestFriendOf(t);
    graph<int, CIT_FAST>      :: TestFriendOf(t);
    graph<int, CIT_GPU>       :: TestFriendOf(t);
    graph<int, CIT_SSE>       :: TestFriendOf(t);
    graph<int, CIT_NOF_TYPES> :: TestFriendOf(t);
}

Это работает на gcc и clang.

Смотрите в прямом эфире http://liveworkspace.org/code/f03c0e25a566a4ca44500f4aaecdd354

PS. Это не точно решает какие-либо проблемы детализации с первого взгляда, но вы можете сделать решение более общим и наследовать из другого "базового" кода вместо жестко закодированного, и в этом случае boileplate может быть скомпенсирован (в случае, если у вас есть много классов, которые должны подружиться с семействами типов графов)

Ответ 4

Класс не может стать другом частичной специализации в соответствии с этим объяснением "non-bug": http://gcc.gnu.org/bugzilla/show_bug.cgi?id=5094

 What clenches it is that 14.5.3, p9
 explicitly prohibits friend declarations of partial specializations:

   -9- Friend declarations shall not declare partial specializations.
       [Example:
           template<class T> class A { };
           class X {
               template<class T> friend class A<T*>; // error
           };
       容nd example]

Но я прихожу к решению, которое не выглядит совершенным, и оно простое в использовании. Идея заключается в создании промежуточного друга внутреннего класса, просто для того, чтобы направить "дружбу" на внешний класс. Недостатком (или преимуществом?) Является то, что нужно обернуть все переменные функции/члена, которые должны быть доступны внешнему другу:

// Different class implementations
enum ClassImplType { CIT_CHECK, CIT_FAST, CIT_GPU, CIT_SSE, CIT_NOF_TYPES } ;

template <typename T>
class vertex;

// Graph class has default template argument CIT_CHECK
template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph {
    typedef typename vertex<T>::template graph_friend<impl_type> graph_friend;
public:
  graph(vertex<T>& a) { graph_friend::foo(a); } // here call private method    
  //...
};

// Vertex class
template <typename T>
class vertex {
  //...
  int foo() {}
public:
  template <ClassImplType impl_type>
  class graph_friend {
     static int foo(vertex& v) { return v.foo(); }
     friend class graph<T,impl_type>;
  };

};
int main() {
    vertex<int> a;
    graph<int,CIT_SSE> b(a);

}