类和函数的模板参数推导之间的区别

art*_*sse 5 c++ templates

问题是什么:

我正在尝试实现一个具有两种专业化的类,一种用于整数类型,一种用于所有其他类型。我想到的第一个版本:


#include <type_traits>
template<typename T, typename std::enable_if_t<std::is_integral<T>::value, bool> = true>
class Test
{
};
template<typename T, typename std::enable_if_t<!std::is_integral<T>::value, bool> = true>
class Test
{
};

Run Code Online (Sandbox Code Playgroud)

但是当我尝试编译上面的代码时,GCC 失败并出现以下错误:


<source>:2:84: error: template parameter 'typename std::enable_if<std::is_integral<_Tp>::value, bool>::type <anonymous>'
template<typename T, typename std::enable_if_t<std::is_integral<T>::value, bool> = true>
                                                                                   ^~~~
<source>:6:85: note: redeclared here as 'typename std::enable_if<(! std::is_integral<_Tp>::value), bool>::type <anonymous>' 
template<typename T, typename std::enable_if_t<!std::is_integral<T>::value, bool> = true>
                                                                                    ^~~~
Run Code Online (Sandbox Code Playgroud)

但是,对函数使用类似的技术进行编译不会出现问题:


#include <type_traits>
template<typename T, typename std::enable_if_t<std::is_integral<T>::value, bool> = true>
void test()
{
}
template<typename T, typename std::enable_if_t<!std::is_integral<T>::value, bool> = true>
void test()
{
}

Run Code Online (Sandbox Code Playgroud)

我想要实现的目标:

  1. 首先,我想了解为什么带函数的版本可以编译,但带类的版本却不能编译。
  2. 我的第二个目标是实现一个满足我一开始指定的条件的类。

我尝试过的:

使用部分特化解决了问题 2:


#include <type_traits>
template<typename T, bool = std::is_integral<T>::value>
class Test;

template<typename T>
class Test<T, true>
{
};
template<typename T>
class Test<T, false>
{
};

Run Code Online (Sandbox Code Playgroud)

但这种方法很糟糕,因为它允许使用Test<float, true>,如果我理解正确(如果我错了,请纠正我),那么将使用整数类型的专门化,这不是我想要的。

总结:

我将以问题的形式重复我的目标:

  1. 为什么带函数的版本可以编译,而带类的版本却不能编译?
  2. 如何实现一个满足我一开始指定的条件的类?

fab*_*ian 2

std::enable_if不能保证在类的模板参数列表中使用,因为该标准仅具有用于函数的 SFINAE,而不具有用于类型的 SFINAE。对于类型,它可能可用,但只能作为非标准编译器扩展。

C++20 在标准中添加了概念,这也适用于模板类上的模板参数。

template<class T>
concept NotIntegral = !std::integral<T>;

template<class T>
class Test;

template<std::integral T>
class Test<T>
{
public:
    void operator()() const
    {
        std::cout << "integral\n";
    }
};

template<NotIntegral T>
class Test<T>
{
public:
    void operator()() const
    {
        std::cout << "not integral\n";
    }
};

int main()
{
    std::cout << "float: ";
    (Test<float>{})();
    std::cout << "int: ";
    (Test<int>{})();
}
Run Code Online (Sandbox Code Playgroud)

对于以前版本的 C++,您可以将 a 添加static_assert到类主体中,这不会阻止用户为第二个模板参数指定无效值,但至少编译代码会产生您选择的编译器错误:

template<typename T>
class Test<T, true>
{
    static_assert(std::is_integral_v<T>,
        "The second template parameter must contain true if and only if the first template parameter is an integral type");
};
Run Code Online (Sandbox Code Playgroud)

您可以引入一个用于创建对象的函数,而不是允许用户创建具有无效模板参数的对象:

template<typename T, bool = std::is_integral<T>::value>
class Test;

template<class T>
Test<T, std::is_integral<T>::value> MakeTest()
{
    return {};
}

template<typename T>
class Test<T, true>
{
    friend Test<T, std::is_integral<T>::value> MakeTest<T>();
    Test() = default;
public:

    void operator()() const
    {
        std::cout << "integral\n";
    }
};
template<typename T>
class Test<T, false>
{
    friend Test<T, std::is_integral<T>::value> MakeTest<T>();
    Test() = default;
public:

    void operator()() const
    {
        std::cout << "not integral\n";
    }
};

int main()
{

    std::cout << "float: ";
    MakeTest<float>()();
    std::cout << "int: ";
    MakeTest<int>()();
    // Test<int, false> test; // would yield a compiler error because of the inaccessible default constructor
}
Run Code Online (Sandbox Code Playgroud)