Чтобы определить друга шаблонного класса с аргументом по умолчанию, нужно ли указывать всех друзей, как показано в коде ниже (что работает)?
// 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>
, но последний, конечно, не работает.
Извиняюсь, если терминология, которую я использую, неверна.
Я могу себе представить, что есть более короткий способ обозначить, что друг определен для всех возможных значений перечисления 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 };
— конец примера ]
Вы можете только подружиться с ними всеми:
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());
}
//...
};
Вы можете шаблон friend
заявление:
template<typename U, ClassImplType V>
friend class graph_foo;
Я пытаюсь выяснить, как сохранить T
— Я обновлю, если узнаю.
Я подумал о следующем способе «исправить это» с помощью рекурсивного наследования.
Встроенные комментарии объясняют, что происходит:
#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. С первого взгляда это точно не решает проблемы многословия, но вы можете сделать решение более общим и наследовать от другого «базового варианта» вместо жестко заданного, и в этом случае кипятильник потенциально может быть компенсирован (в случае, если у вас много классов, которым нужно подружиться с семействами типов графов)
Класс не может стать другом частичной специализации в соответствии с этим объяснением «без ошибок»: 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);
}