"function template has already been defined" 使用 "std::enable_if_t"

"function template has already been defined" using "std::enable_if_t"

我正在尝试使用 std::enable_if_t 根据传递给它的类型的特征来切换模板函数的实现。

这是一个例子:

#include <iostream>
#include <type_traits>

// Enable if "T" is integral
template <typename T,
    typename = std::enable_if_t<std::is_integral_v<T>>
>
void print(T value)
{
    std::cout << "Integral: " << value << std::endl;
}

// Enable if "T" is not integral
template <typename T,
    typename = std::enable_if_t<!std::is_integral_v<T>>
>
void print(T value)
{
    std::cout << "Not Integral: " << value << std::endl;
}

int main()
{
    int i = 42;
    print(i);

    double d = 42.0;
    print(d);
}

问题是,编译失败说明:

'void print(T)': function template has already been defined.

我觉得这很奇怪,因为 std::is_integral_v<T>!std::is_integral_v<T> 永远不会同时评估为真,所以只要启用一个实现,就应该禁用另一个。

为什么这不起作用?解决这个问题并获得我正在寻找的功能的最佳方法是什么?

CPP Reference:

A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).

/*** WRONG ***/

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              typename = std::enable_if_t<std::is_integral<Integer>::value>
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              typename = std::enable_if_t<std::is_floating_point<Floating>::value>
    >
    T(Floating) : m_type(float_t) {} // error: treated as redefinition
};

/* RIGHT */

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              std::enable_if_t<std::is_integral<Integer>::value, int> = 0
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
    >
    T(Floating) : m_type(float_t) {} // OK
};

Care should be taken when using enable_if in the type of a template non-type parameter of a namespace-scope function template. Some ABI specifications like the Itanium ABI do not include the instantiation-dependent portions of non-type template parameters in the mangling, meaning that specializations of two distinct function templates might end up with the same mangled name and be erroneously linked together. For example:

// first translation unit

struct X {
    enum { value1 = true, value2 = true };
};

template<class T, std::enable_if_t<T::value1, int> = 0>
void func() {} // #1

template void func<X>(); // #2

// second translation unit

struct X {
    enum { value1 = true, value2 = true };
};

template<class T, std::enable_if_t<T::value2, int> = 0>
void func() {} // #3

template void func<X>(); //#4

The function templates #1 and #3 have different signatures and are distinct templates. Nonetheless, #2 and #4, despite being instantiations of different function templates, have the same mangled name in the Itanium C++ ABI (_Z4funcI1XLi0EEvv), meaning that the linker will erroneously consider them to be the same entity.

修复:

#include <iostream>
#include <type_traits>

// Enable if "T" is integral
template <typename T,
    std::enable_if_t<std::is_integral_v<T>, int> = 0
>
void print(T value)
{
    std::cout << "Integral: " << value << std::endl;
}

// Enable if "T" is not integral
template <typename T,
    std::enable_if_t<!std::is_integral_v<T>, int> = 0
>
void print(T value)
{
    std::cout << "Not Integral: " << value << std::endl;
}

int main()
{
    int i = 42;
    print(i);

    double d = 42.0;
    print(d);
}

更好的是,使用概念:

#include <iostream>
#include <concepts>

// Enable if "T" is integral
template <std::integral T>
void print(T value)
{
    std::cout << "Integral: " << value << std::endl;
}

// Enable if "T" is not integral
template <typename T>
void print(T value)
{
    std::cout << "Not Integral: " << value << std::endl;
}

int main()
{
    int i = 42;
    print(i);

    double d = 42.0;
    print(d);
}

LIVE