用SFINAE检测constexpr

时间:2013-03-05 19:47:06

标签: c++ templates c++11 sfinae constexpr

我正在努力升级一些C ++代码以利用C ++ 11中的新功能。我有一个特性类,其中有一些函数返回基本类型,这些函数大部分时间(但并不总是)返回一个常量表达式。我想根据函数是否为constexpr来做不同的事情。我提出了以下方法:

template<typename Trait>
struct test
{
    template<int Value = Trait::f()>
    static std::true_type do_call(int){ return std::true_type(); }

    static std::false_type do_call(...){ return std::false_type(); }

    static bool call(){ return do_call(0); }
};

struct trait
{
    static int f(){ return 15; }
};

struct ctrait
{
    static constexpr int f(){ return 20; }
};

int main()
{
   std::cout << "regular: " << test<trait>::call() << std::endl;
   std::cout << "constexpr: " << test<ctrait>::call() << std::endl;
}

额外的int / ...参数是这样的,如果两个函数在 SFINAE 之后可用,则第一个函数会通过重载分辨率来选择。

使用 Clang 3.2 编译并运行它会显示:

regular: 0
constexpr: 1

所以这似乎有效,但我想知道代码是否合法C ++ 11。特别是因为我的理解是 SFINAE 的规则已经改变了。

2 个答案:

答案 0 :(得分:13)

  

注意: I opened a question here关于OP代码是否实际有效。我在下面重写的例子无论如何都适用。


  

但我想知道代码是否合法C ++ 11

虽然默认模板参数可能被认为有点不寻常。我个人更喜欢以下风格,这类似于你(阅读:I)将特征写入check for a function's existence,只使用非类型模板参数并省略decltype

#include <type_traits>

namespace detail{
template<int> struct sfinae_true : std::true_type{};
template<class T>
sfinae_true<(T::f(), 0)> check(int);
template<class>
std::false_type check(...);
} // detail::

template<class T>
struct has_constexpr_f : decltype(detail::check<T>(0)){};

Live example.


解释时间〜

您的原始代码是,因为默认模板参数的实例化点是其功能模板的实例化点,在您的情况下,在main中,因此它可以'早于那个被替换。

§14.6.4.1 [temp.point] p2

  

如果以一种使用该函数模板的默认参数定义的方式调用函数模板[...],则默认参数的实例化点是实例化的实例。功能模板[...]。

之后,这通常是SFINAE规则。


†至少我认为如此,标准中并没有完全明确。

答案 1 :(得分:1)

由@ marshall-clow提示,我将一个更为通用的类型特征版本用于检测constexpr。我在std::invoke_result上对其进行了建模,但由于constexpr取决于输入,因此模板参数用于传入的值,而不是类型。

它有点受限,因为模板args只能是limited set of types,并且当它们到达方法调用时它们都是 const 。如果需要其他类型,可以轻松测试constexpr包装器方法,或者参考参数可以非常数左值

更多的是练习和演示,而不是实际有用的代码。

使用template<auto F, auto... Args>使其成为仅限C ++ 17,需要gcc 7或clang 4. MSVC 14.10.25017无法编译它。

namespace constexpr_traits {

namespace detail {

// Call the provided method with the provided args.
// This gives us a non-type template parameter for void-returning F.
// This wouldn't be needed if "auto = F(Args...)" was a valid template
// parameter for void-returning F.
template<auto F, auto... Args>
constexpr void* constexpr_caller() {
    F(Args...);
    return nullptr;
}

// Takes a parameter with elipsis conversion, so will never be selected
// when another viable overload is present
template<auto F, auto... Args>
constexpr bool is_constexpr(...) { return false; }

// Fails substitution if constexpr_caller<F, Args...>() can't be
// called in constexpr context
template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
constexpr bool is_constexpr(int) { return true; }

}

template<auto F, auto... Args>
struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};

}

Live demo with use-cases on wandbox