有没有一种方法可以将模板的替换失败转换为bool值(true / false)或标签(std :: true_type / std :: false_type)

时间:2018-12-21 09:42:04

标签: c++ c++17 sfinae

我正在以C ++ 17风格在SFINAE上“玩”一点,我试图获得这样的结果:替换失败而不是仅仅导致编译器移至下一种情况,而是导致“编译器”(相反,经过此类尝试的类)(通过false / std::false_type)报告替换失败,然后编译器移至下一种情况。

我目前的尝试归结为这样的事情:

template <typename T>
class logic_value
{
    static constexpr bool result = std::is_same<
        std::enable_if_t<T, std::true_type>,
        std::true_type>;
};

template <typename T>
inline constexpr bool lv = logic_value<T>::result;

(用法示例:)

template <typename T>
std::enable_if_t<
    lv<decltype(std::declval<T>() + std::declval<T>())> /* has plus operator */ 
    && 
    (lv<decltype(std::declval<T>().func_foo())> || lv<decltype(std::declval<T>().func_bar())>) /*has either func_foo() or func_bar() */ 
    &&
    lv<T&> /* can have a reference */
    && 
    (!lv<decltype(std::declval<T>().func_foobar())>) /* does NOT have a func_foobar() */
, T> const & Ioo(T const &);

但是它不像我想象的那样起作用...:/ 当我使用logic not!)运算符来确保测试类型中的某些内容不存在时,尤其棘手。

2 个答案:

答案 0 :(得分:3)

执行此操作的方法是使用检测习惯用法,您希望is_detected具有适当的别名。

在您的特定情况下:

template <typename T> using has_plus_t = decltype(std::declval<T>() + std::declval<T>());
template <typename T> using func_foo_t = decltype(std::declval<T>().func_foo());
template <typename T> using func_bar_t = decltype(std::declval<T>().func_bar());
template <typename T> using func_foobar_t = decltype(std::declval<T>().func_foobar());

template <typename T>
enable_if_t<
    is_detected_v<has_plus_t, T>
    && 
    (is_detected_v<func_foo_t, T> || is_detected_v<func_bar_t, T>)
    &&
    is_detected_v<add_lvalue_reference_t, T>
    && 
    !is_detected_v<func_foobar_t, T>
, T> const & Ioo(T const &);

您无法在这种情况下拥有要测试的表达式,因为一旦失败,整个事情就会失败。您的逻辑允许并要求某些表达式失败-因此,您需要控制所有表达式的实例化这些表达式。这就是检测惯用语的目的。

答案 1 :(得分:2)

logical_value的问题在于,在发生替换错误的情况下,您没有为编译器提供替代路径。

我是这样做的:

#include <utility>
#include <array>

template<typename T, typename U>
constexpr auto check_addition(int) -> decltype(std::declval<T>() + std::declval<U>() , std::true_type{});

template<typename T, typename U>
constexpr std::false_type check_addition(...);

template<typename T, typename U>
constexpr bool can_add = decltype(check_addition<T, U>(0))::value;

int main() {

    static_assert(can_add<int, float>);
    static_assert(!can_add<std::string, float>);

}

这个想法是利用两个重载,一个重载特定类型的参数(在我的情况下为int),另一个重载省略号。当我们使用int作为参数调用该重载函数时,编译器将首先在必须执行所需检查的地方检查int重载。

逗号运算符用于提供true_type作为返回类型,在强制转换检查成功完成时。

如果第一个重载SFINAE失败,则选择第二个重载,该重载始终返回false_type