提问人:user1806566 提问时间:10/10/2023 最后编辑:Jan Schultkeuser1806566 更新时间:10/13/2023 访问量:181
如何将函数模板参数限制为特定类模板的专用化
How to restrict function template arguments to be specializations of a specific class template
问:
我有一个这样的结构:
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 };
}
之所以存在,是因为函数模板可以推断出类型,因此用户客户端不必显式指定它。我意识到 C++17 有 CTAD 可以更好地解决这个问题。
只要传入的类型确实是 的专用化,这就可以正常工作,但如果不是,结果将是 中的一些错误。make_uses_template_class
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 { ... };
但我在几个线程中看到,通常不赞成对类使用这种启用程序,并且通常建议这样做。我知道普遍的共识是可以给出更好的错误消息,并且它不会像用户为默认模板参数指定类型那样被滥用。不幸的是,我认为不可能为 TClass::template_class_tag 类型是否存在编写静态断言。static_assert
static_assert
为了解决这个问题,我想我可以给出一个非模板库,并使用带有 .我认为这会起作用,尽管它有点侵入性(基类没有其他目的)。TemplateClass
std::is_base_of
有没有一个普遍接受的成语来限制这样的类?UsesTemplateClass
该函数也有同样的问题,但我知道启用程序等在函数中的使用方式通常与类中的使用方式不同,所以我也想问这个问题。
答:
我想做的是确保如果 TClass 不是 TemplateClass,则不存在 make_template_class 重载。此外,如果有人手动尝试使用不是 TemplateClass 的参数实例化 UsesTemplateClass,我希望错误消息是合理的。
方法 1
一个简单的解决方案是将模板定义更改为:
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);
方法 2
您也可以使用类似类型特征的逻辑来强制执行您的要求。
在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);
更新
Jan Schultke 提供了更简洁的 at https://godbolt.org/z/d68WrzTcP 实现。is_template_class
下面是该链接中的代码:
template <typename T>
struct is_template_class
{
static std::false_type test(...);
template <typename A, typename B>
static std::true_type test(TemplateClass<A, B>*);
static constexpr bool value = decltype(test(static_cast<T*>(nullptr)))::value;
};
static_assert(is_template_class<TemplateClass<int, float>>::value, "");
static_assert(!is_template_class<int>::value, "");
评论
TemplateClass
您可以添加类型特征:
#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};
}
或者,如果要重载,则使用 SFINAE,其中基于 -的类型不是模板参数:make_uses_template_class
TemplateClass
template <typename TClass>
std::enable_if_t<
is_TemplateClass_v<std::remove_cv_t<std::remove_reference_t<TClass>>>,
UsesTemplateClass<TClass>>
make_uses_template_class(TClass& instance) {
return UsesTemplateClass<TClass>{instance};
}
评论
std::array
正如 “R Sahu” 在他的“方法 1”代码示例中已经指出的那样,如果只允许“TemplateClass”的专用化,则不确定为什么“TClass”是任意类型。为什么不遵循他的基本“方法1”或类似方法。如果“TClass”必须是任意类型(无论出于何种原因),那么下面的代码可以用作他的“方法 2”代码示例的更通用的替代方案(TBH 我没有详细阅读他的代码,但下面是一种通用技术,您可以用于任何仅采用基于类型的模板参数的模板 - 请参阅下面代码中的“IsSpecialization”注释 - 单击此处运行它):
#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;
}
评论
std::array
评论