如何判断一个类型是否是特定模板类的实例?

17
我有一个函数,它需要一个模板类型来确定返回值。有没有办法在编译时判断模板类型是否是某个模板类的实例化?
例如:
class First { /* ... */ };

template <typename T>
class Second { /* ... */ };

using MyType = boost::variant<First, Second<int>, Second<float>>;

template <typename SecondType>
auto func() -> MyType {
    static_assert(/* what goes here?? */, "func() expects Second type");
    SecondType obj;
    // ...
    return obj;
}

MyType obj = func<Second<int>>();

我知道可以通过以下方式解决这个问题

template <typename T>
auto func() -> MyType {
    static_assert(std::is_same<T, int>::value || std::is_same<T, float>::value,
                  "func template must be type int or float");

    Second<T> obj;
    // ...
    return obj;
}

MyType obj = func<int>();

我只是好奇是否有一种方法可以测试类型是否是模板类的实例化?因为如果MyType最终有6个Second实例,我不想测试所有可能的类型。


你能改变 Second 吗?如果你可以添加一个 std :: true_type isSecond 成员,这件事很容易。 - Caleth
4个回答

22

这里有一个选项:

#include <iostream>
#include <type_traits>
#include <string>

template <class, template <class> class>
struct is_instance : public std::false_type {};

template <class T, template <class> class U>
struct is_instance<U<T>, U> : public std::true_type {};

template <class>
class Second 
{};

int main()
{
    using A = Second<int>;
    using B = Second<std::string>;
    using C = float;
    std::cout << is_instance<A, Second>{} << '\n'; // prints 1
    std::cout << is_instance<B, Second>{} << '\n'; // prints 1
    std::cout << is_instance<C, Second>{} << '\n'; // prints 0
}

这基本上是针对模板实例化类型的is_instance结构进行专门化。


7
最好将其改为 template <class, template <class...> class> struct is_instance,这样您就不会受到仅具有一个参数的模板的限制。另外,请注意,此方法仅适用于具有类型名称参数的模板。 - Henri Menke

11

另一个选择,接着Henri的评论:

#include <iostream>
#include <type_traits>
#include <string>

template <class, template <class, class...> class>
struct is_instance : public std::false_type {};

template <class...Ts, template <class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};

template <class>
class Second 
{};

template <class, class, class>
class Third 
{};

int main()
{
    using A = Second<int>;
    using B = Second<std::string>;
    using C = float;
    using D = Third<std::string, int, void>;
    std::cout << is_instance<A, Second>{} << '\n'; // prints 1
    std::cout << is_instance<B, Second>{} << '\n'; // prints 1
    std::cout << is_instance<C, Second>{} << '\n'; // prints 0
    std::cout << is_instance<D, Third>{} << '\n'; // prints 1
}

1
当模板参数为整数类型时,这似乎无法工作。 例如:is_instance<std::array<int, 3>, std::array>无法编译。你知道如何解决吗? - user3520616
2
@user3520616,问题在于3是一个非类型模板参数。我们需要将其提升为一种类型(例如std::integral_constant)。这意味着需要为std::array提供特化或增加is_instance的复杂度。我会考虑一下。 - Richard Hodges

7

对于@RichardHodges答案的另一个改进:通常,人们不仅想捕获普通类型,而且还想捕获所有cv限定符ref限定符的类型。

换句话说,如果is_instance<A, Second>{}返回true,那么is_instance<A const&, Second>{}is_instance<A&&, Second>{}也应该返回true。本线程中当前的实现不支持这一点。

以下代码通过添加另一个间接层和一个std :: remove_cvref_t来考虑所有的cv-ref-qualified类型:

namespace
{
    template <typename, template <typename...> typename>
    struct is_instance_impl : public std::false_type {};

    template <template <typename...> typename U, typename...Ts>
    struct is_instance_impl<U<Ts...>, U> : public std::true_type {};
}

template <typename T, template <typename ...> typename U>
using is_instance = is_instance_impl<std::remove_cvref_t<T>, U>;

将其用作

#include <iostream>

template <typename ...> struct foo{};
template <typename ...> struct bar{};

int main()
{
    std::cout << is_instance<foo<int>, foo>{} << std::endl;           // prints 1
    std::cout << is_instance<foo<float> const&, foo>{} <<std::endl;   // prints 1
    std::cout << is_instance<foo<double,int> &&, foo>{} << std::endl; // prints 1
    std::cout << is_instance<bar<int> &&, foo>{} << std::endl;        // prints 0
}

我已经点赞了,但为了与例如std::is_base_of保持一致,我会使用std::remove_cv_t而不是std::decay_tstd::is_base_of不会将引用视为另一个类型的基类或派生类。 - Max
有什么想法可以添加 is_instance_v 版本吗? - Youda008
@Youda008:is_instance 基本上就是标准库中所谓的 "_v"。否则你就必须在某个地方使用 ::value。因此,在标准库中,我的 is_instance_impl 就会被称为 is_instance,而我的 is_instance 就会被称为标准库中的 is_instance_v - davidhigh
@Max:在 C++20 中,我们现在有了 std::remove_cvref,它基本上就是我想要的 std::decay - davidhigh
@davidhigh 但是你必须在你的例子中使用 ::value。你的 is_instance 会产生 std::false_typestd::true_type,它们都是带有成员 value 的结构体。一个 _v 变量必须声明为 inline constexpr bool ... 而不是 using ... = - Youda008
显示剩余2条评论

1

这适用于 int 模板参数。

template <class, template <int...> class>
struct is_instance : public std::false_type {};

template <int...Ts, template <int...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接