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

How to restrict function template arguments to be specializations of a specific class template

提问人:user1806566 提问时间:10/10/2023 最后编辑:Jan Schultkeuser1806566 更新时间:10/13/2023 访问量:181

问:

我有一个这样的结构:

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_classmake_uses_template_classTemplateClassUsesTemplateClass

我想确保不存在重载,如果不是.另外,我希望错误消息是合理的。 我知道有几种方法可以做到这一点,但我没有看到很多关于如何使用推动因素或在这种情况下的一致指导。make_template_classTClassTemplateClassstatic_asserts

例如,关于这门课,我想我可以做这样的事情:

template <typename TClass>
class UsesTemplateClass;  // declared but not defined

template <typename Arg1, typename Arg2>
class UsesTemplateClass<Arg1, Arg2> {
   // real definition
};

这将起作用(如果您使用 ,而不是 ,它会抱怨不存在)。我并不感到兴奋,因为我必须在我的专业化中明确指定参数,因为在一般情况下,可能会有多个模板参数可能会发生变化。TemplateClassUsesTemplateClass<SomeOtherType>TemplateClass

或者,我的想法是把类似的东西放进去,然后定义为:using template_class_tag = voidTemplateClassUsesTemplateClass

template <typename TClass,
          typename = typename TClass::template_class_tag >
class UsesTemplateClass { ... };

但我在几个线程中看到,通常不赞成对类使用这种启用程序,并且通常建议这样做。我知道普遍的共识是可以给出更好的错误消息,并且它不会像用户为默认模板参数指定类型那样被滥用。不幸的是,我认为不可能为 TClass::template_class_tag 类型是否存在编写静态断言。static_assertstatic_assert

为了解决这个问题,我想我可以给出一个非模板库,并使用带有 .我认为这会起作用,尽管它有点侵入性(基类没有其他目的)。TemplateClassstd::is_base_of

有没有一个普遍接受的成语来限制这样的类?UsesTemplateClass

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

C++ C++14 SFINAE 类模板

评论


答:

3赞 R Sahu 10/11/2023 #1

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

更新以使用 .UsesTemplateClassstatic_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, "");

评论

1赞 Jan Schultke 10/12/2023
我发现这种检测非常规和复杂专业化的方法很复杂。这是一种更简单的方法。就我个人而言,我仍然会使用 Ted 的方法,使用类模板的部分专业化。TemplateClass
0赞 R Sahu 10/12/2023
@JanSchultke。感谢您的输入和代码。
4赞 Ted Lyngmo 10/11/2023 #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};
}

演示


或者,如果要重载,则使用 SFINAE,其中基于 -的类型不是模板参数:make_uses_template_classTemplateClass

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

评论

0赞 Larry 10/11/2023
是的,基本上是正确的方法,尽管请参阅我自己的回应以获取更通用的版本(您自己的解决方案可以包装它)。请注意,顺便说一句,您的“_v”帮助变量在运算所需的 C++14 中不起作用(从 C++17 开始支持)
0赞 Ted Lyngmo 10/11/2023
@Larry 变量模板从 C++14 开始可用 - 当然,它可以更通用一些,但为了真正通用,它必须支持非类型模板参数的混合(例如支持),这实在是太多了。std::array
0赞 Larry 10/11/2023
是的,你是对的,错过了“静态”。它是“内联”变量,直到 C++17 才受支持。也同意(以前知道)非类型模板参数的情况,但上次我检查时(由于当前的语言限制)尝试处理这些情况目前也不可行。无论如何,就目前而言,恕我直言,许多(可能是大多数)仅处理类型参数的主流案例是首选方法。
0赞 Ted Lyngmo 10/11/2023
@Larry 也许可以对模板参数进行去酸洗以允许混合,但这需要更多的工作,而不是这个 i.m.o.
0赞 Larry 10/11/2023
然而,我很久以前就研究过一次,IIRC 有一些问题使它变得非常困难,如果不是不可能的话
2赞 Larry 10/11/2023 #3

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

评论

0赞 Ted Lyngmo 10/11/2023
不错,但“可用于任何模板的通用技术”并不完全正确。尝试使用 - 和变量模板从 C++14 开始可用,而不是 C++17。std::array
0赞 Larry 10/11/2023
具有讽刺意味的是,我考虑过在评论中明确表示它不会处理非类型参数,但不想进入关于其行为的论文(就本文而言,这是题外话)。我想无论如何,没有人会注意到,至少在帖子变得陈旧之前不会。非常感谢:)
0赞 Larry 10/11/2023
我指的是“内联”变量。它们直到 C++17 才可用。
0赞 user1806566 10/11/2023
值得一提的是,UsesTemplateClass 将 TClass 作为模板参数而不是 TemplateClass 的模板参数的原因是,在我的应用程序中,TemplateClass 可能会接受许多参数。参数会随着代码的发展而变化,因此从维护的角度来看,我不想在 UseTemplateClass 类(其中有几个)中对这些参数列表进行硬编码。
0赞 Ted Lyngmo 10/11/2023
@user1806566我明白了。从一开始就知道这一点会很好。