我的结构如下:
template <typename Arg1, typename Arg2>
class TemplateClass { ... };
template <typename TClass>
class UsesTemplateClass {
public:
UsesTemplateClass( TClass& instance ) inst{instance} { ... }
private:
TClass& inst;
};
template <typename TClass>
auto make_uses_template_class( TClass& instance ) {
return UsesTemplateClass<TClass>{ instance };
}
make_uses_template_class
存在的原因是函数模板可以推导类型,因此用户客户端不必显式指定它。我意识到 C++17 有 CTAD 可以更好地解决这个问题。
只要传入 make_uses_template_class
的类型确实是 TemplateClass
的特化,就可以正常工作,但如果不是,则结果会在 UsesTemplateClass
中出现一些错误。
我想确保如果
make_template_class
不是 TClass
,则不存在 TemplateClass
重载。另外,我希望错误消息是合理的。
我知道有多种方法可以做到这一点,但我没有看到很多关于如何在这种情况下使用启用程序或 static_asserts
的一致指导。
例如,关于课程,我想我可以做这样的事情:
template <typename TClass>
class UsesTemplateClass; // declared but not defined
template <typename Arg1, typename Arg2>
class UsesTemplateClass<Arg1, Arg2> {
// real definition
};
这会起作用(如果你用
TemplateClass
以外的任何东西实例化它,它会抱怨 UsesTemplateClass<SomeOtherType>
不存在)。我对必须在我的专业化中显式指定 TemplateClass
的参数感到不高兴,因为在一般情况下,可能有几个模板参数可能会发生变化。
或者,我有一个想法,将
using template_class_tag = void
之类的东西放入 TemplateClass
中,然后将 UsesTemplateClass
定义为:
template <typename TClass,
typename = typename TClass::template_class_tag >
class UsesTemplateClass { ... };
但我在几个线程中看到,对类使用这种类型的启用器通常不受欢迎,并且通常建议使用
static_assert
。据我所知,普遍的共识是 static_assert
可以提供更好的错误消息,并且它不会像用户为默认模板参数指定类型那样被滥用。不幸的是,我不相信可以编写一个静态断言来判断 TClass::template_class_tag 类型是否存在。
为了解决这个问题,我想我可以给
TemplateClass
一个非模板库,并使用 std::is_base_of
的静态断言。我认为这会起作用,尽管它有点侵入性(基类没有其他目的)。
是否有一个普遍接受的习惯用法来以这种方式限制像
UsesTemplateClass
这样的类?
该函数也有同样的问题,但我知道启用器等在函数中的使用方式通常与在类中不同,所以我也想问一下。
正如“R Sahu”已经在他的“Approach 1”代码示例中指出的那样,不确定如果只允许“TemplateClass”的特化,为什么“TClass”是任意类型。为什么不遵循他的基本“方法 1”或类似方法。如果“TClass”必须是任意类型(无论出于何种原因),那么以下代码可以用作他的“Approach 2”代码示例的更通用的替代方案(说实话,我没有详细阅读他的代码,但是以下是一种通用技术,您可以将其用于任何仅采用基于类型的模板参数的模板 - 请参阅下面代码中的“IsSpecialization”注释 - 单击 here 运行它):
#include <type_traits>
/////////////////////////////////////////////////////////////////////////////
// IsSpecialization. Primary template. See partial specialization just below
// for details.
/////////////////////////////////////////////////////////////////////////////
template <typename,
template<typename...> class>
struct IsSpecialization : public std::false_type
{
};
/////////////////////////////////////////////////////////////////////////////
// Partial specialization of (primary) template just above. The following
// kicks in when the 1st template arg in the primary template above (a type)
// is a specialization of the 2nd template arg (a template). IOW, this partial
// specialization kicks in if the 1st template arg (a type) is a type created
// from a template given by the 2nd template arg (a template). If not then the
// primary template kicks in above instead (i.e., when the 1st template arg (a
// type) isn't a type created from the template given by the 2nd template arg
// (a template), meaning it's not a specialization of that template. Note that
// "IsSpecialization" can handle templates taking type-based template args only
// (handling non-type args as well is very difficult if not impossible in current
// versions of C++)
//
// Example
// -------
// template <class T>
// class Whatever
// {
// // Make sure type "T" is a "std::vector" instance
// static_assert(IsSpecialization<T, std::vector>::value,
// "Invalid template arg T. Must be a \"std::vector\"");
// };
//
// Whatever<std::vector<int>> whatever1; // "static_assert" above succeeds ("T" is a "std::vector")
// Whatever<std::list<int>> whatever2; // "static_assert" above fails ("T" is *not* a "std::vector")
/////////////////////////////////////////////////////////////////////////////
template <template<typename...> class Template,
typename... TemplateArgs>
struct IsSpecialization<Template<TemplateArgs...>, Template> : public std::true_type
{
};
template <typename Arg1, typename Arg2>
class TemplateClass
{
};
template <typename TClass>
class UsesTemplateClass
{
/////////////////////////////////////////////////////////////////
// You can even create a wrapper for this particular call to
// "IsSpecialization" that specifically targets "TemplateClass"
// if you wish (to shorten the syntax a bit but I leave that to
// you as an exercise). Note that in C++17 or later you should
// also create the usual "IsSpecialization_v" helper variable
// for "IsSpecialization" (can also be done in C++14 but "_v"
// variables in <type_traits> itself is a C++17 feature and
// they're declared "inline" which is also a C++17 feature, so
// such variables in your own code is more consistent with C++17
// IMHO and therefore less confusing), and in C++20 or later a
// "concept" for it as well (all this getting off-topic though).
/////////////////////////////////////////////////////////////////
static_assert(IsSpecialization<TClass, TemplateClass>::value,
"Invalid template arg \"TClass\". Must be a \"TemplateClass\" specialization");
public:
UsesTemplateClass(TClass &instance)
: inst{instance}
{
// ...
}
private:
TClass& inst;
};
template <typename TClass>
auto make_uses_template_class( TClass& instance )
{
return UsesTemplateClass<TClass>{ instance };
}
int main()
{
// Compiles ok ("tc" is a "TemplateClass" specialization)
TemplateClass<int, double> tc;
auto utc1 = make_uses_template_class(tc);
UsesTemplateClass<decltype(tc)> utc2(tc);
// Triggers "static_assert" above ("i" is not a "TemplateClass" specialization)
int i;
auto utc3 = make_uses_template_class(i);
UsesTemplateClass<decltype(i)> utc4(i);
return 0;
}
我想做的是确保如果 TClass 不是 TemplateClass,则 make_template_class 重载不存在。另外,如果有人尝试使用不是 TemplateClass 的参数手动实例化 UsesTemplateClass,我希望错误消息是合理的。
一个简单的解决方案是将模板定义更改为:
template <typename T1, typename T2>
class UsesTemplateClass {
public:
UsesTemplateClass( TemplateClass<T1, T2>& instance ) : inst{instance} {}
private:
TemplateClass<T1, T2>& inst;
};
template <typename T1, typename T2>
auto make_uses_template_class( TemplateClass<T1, T2>& instance ) {
return UsesTemplateClass<T1, T2>{ instance };
}
进行此更改后,以下内容将起作用:
TemplateClass<int, double> t;
auto a = make_uses_template_class(t);
auto b = UsesTemplateClass<int, double>(t);
但是以下会导致编译器错误:
int i = 0;
auto a = make_uses_template_class(i);
auto b = UsesTemplateClass<int>(i);
您也可以使用类似类型特征的逻辑来强制执行您的要求。
在
TemplateClass
之后添加以下内容作为支持代码
struct is_template_class_imp
{
struct __two {char _; char __;};
template <class t> static __two __test(t* ptr);
template <class t1, class t2> static char __test(TemplateClass<t1, t2>* ptr);
};
template <typename T>
struct is_template_class : std::integral_constant<bool, sizeof(is_template_class_imp::__test((T*)0)) == 1> {};
更新
UsesTemplateClass
以使用 static_assert
。
template <typename TClass>
class UsesTemplateClass {
public:
static_assert(is_template_class<TClass>::value, "Template parameter needs to be a TemplatClass");
UsesTemplateClass( TClass& instance ) : inst{instance} {}
private:
TClass& inst;
};
通过这些更新,以下内容将起作用:
TemplateClass<int, double> t;
auto a = make_uses_template_class(t);
auto b = UsesTemplateClass<int, double>(t);
但是以下会导致编译器错误:
int i = 0;
auto a = make_uses_template_class(i);
auto b = UsesTemplateClass<int>(i);
您可以添加类型特征:
#include <type_traits>
template <class...> // primary
struct is_TemplateClass : std::false_type {};
template <class A, class B> // specialization
struct is_TemplateClass<TemplateClass<A, B>> : std::true_type {};
template <class T> // helper variable template
static constexpr bool is_TemplateClass_v = is_TemplateClass<T>::value;
您可以在
static_assert
中使用它来使错误消息清晰:
template <typename TClass>
auto make_uses_template_class(TClass& instance) {
static_assert(
is_TemplateClass_v<std::remove_cv_t<std::remove_reference_t<TClass>>>,
"Not a TemplateClass");
return UsesTemplateClass<TClass>{instance};
}