使用constexpr bool测试的enable_if无法正常工作

时间:2018-07-19 21:53:05

标签: c++ c++11 templates constexpr enable-if

我有一个数学函数,希望能够接受双精度或双精度的数组/向量/容器,并且行为略有不同。

我正在尝试使用SFINAE并键入traits以选择正确的功能。

这是一个最小的示例:

#include <iostream>
#include <vector>
#include <type_traits>

template <typename T>
constexpr bool Iscontainer()
{
    if constexpr (std::is_class<T>::value && std::is_arithmetic<typename T::value_type>::value) {
        return true;
    }
    return false;
}

// Function 1 (double):
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type g(T const & t)
{
    std::cout << "this is for a double" << t << std::endl;
}

// Function 2 (vec), version 1:
template <typename T>
typename std::enable_if<IsContainer<T>()>::type g(T const & t)
{
    std::cout << "this is for a container" << t[0] << std::endl;
}

int main()
{
    std::vector<double> v {1, 2};
    std::array<double, 2> a {1, 2};
    double d {0.1};

    g<>(v);
    g<>(a);
    g<>(d);  // error here
}

我收到一个编译时错误:

../main.cpp:8:47: error: ‘double’ is not a class, struct, or union type
     if constexpr (std::is_class<T>::value && std::is_arithmetic<typename     T::value_type>::value) {
                   ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

但是,当我将功能2替换为:

// Function 2 (vec), version 2:
template <typename T>
typename std::enable_if<std::is_class<T>::value && std::is_arithmetic<typename T::value_type>::value>::type
g(T const & t)
{
    std::cout << "this is for a vector" << t[0] << std::endl;
}

有效。

我的问题是我不明白为什么第一个版本不起作用。 而且我更喜欢第一个版本的可读性。

2 个答案:

答案 0 :(得分:1)

失败的原因很简单。您没有调用SFINAE,并且当编译器尝试评估表达式时,它会看到:

if constexpr (std::is_class<double>::value // this is fine it's false
   && std::is_arithmetic<typename double::value_type>::value // problem here!
)

对整个语句进行求值,如果没有则短路。与您当前拥有的解决方案最接近的解决方案是显式拆分if,以便在T不是类并且第二次检查没有意义时将有问题的部分丢弃。

#include <iostream>
#include <vector>
#include <type_traits>

template <typename T>
constexpr bool IsVector()
{
    if constexpr (std::is_class<T>::value) {
        if constexpr (std::is_arithmetic<typename T::value_type>::value) {
            return true;
        }
    }
    return false;
}

// Function 1 (double):
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type g(T const & t)
{
    std::cout << "this is for a double" << t << std::endl;
}

// Function 2 (vec), version 1:
template <typename T>
typename std::enable_if<IsVector<T>()>::type g(T const & t)
{
    std::cout << "this is for a vector" << t[0] << std::endl;
}

int main()
{
    std::vector<double> v {1, 2};
    double d {0.1};

    g<>(v);
    g<>(d);  // error here
}

或者,我建议使用using别名:

template <typename T>
using IsVector2 = std::conjunction<typename std::is_class<T>, std::is_arithmetic<typename T::value_type> >;

template <typename T>
typename std::enable_if<IsVector2<T>::value>::type g(T const & t)
{
    std::cout << "this is for a vector" << t[0] << std::endl;
}

您也可以更好地命名。它并不会真正检查Tvector还是容器(在您编辑之后)。您当前的定义也有点宽松。

答案 1 :(得分:0)

似乎简单的重载就可以完成工作:

template <typename T>
void g(T const & t)
{
    std::cout << "this is for a double" << t << std::endl;
}

template <typename T>
void g(const std::vector<T>& t)
{
    std::cout << "this is for a vector" << t[0] << std::endl;
}

Demo

您的问题是T::value_type的评估结果是double 在进行短路评估之前,您遇到了硬错误(SFINAE在那里没有发生)。 您可以将此条件放在constexpr部分。

您可能会重写函数:

template <typename T>
constexpr bool IsVector()
{
    if constexpr (std::is_class<T>::value) {
        if constexpr (std::is_arithmetic<typename T::value_type>::value) {
            return true;
        }
    }
    return false;
}

Demo

您的第二个版本使用SFINAE

// Function 2 (vec), version 2:
template <typename T>
typename std::enable_if<std::is_class<T>::value
                        && std::is_arithmetic<typename T::value_type>::value>::type
// SFINAE happens here for double                      ^^^^^^^^^^^^^
g(T const & t)