如何将函数模板参数限制为特定类模板的特化

问题描述 投票:0回答:3

我的结构如下:

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
这样的类?

该函数也有同样的问题,但我知道启用器等在函数中的使用方式通常与在类中不同,所以我也想问一下。

c++ templates c++14 sfinae class-template
3个回答
2
投票

正如“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;
}

2
投票

我想做的是确保如果 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);

2
投票

您可以添加类型特征:

#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};
}

演示

© www.soinside.com 2019 - 2024. All rights reserved.