用 SFINAE 检测 constexpr

2021-12-13 00:00:00 templates c++ c++11 constexpr sfinae

我正在升级一些 C++ 代码以利用 C++11 中的新功能.我有一个 trait 类,其中有几个函数返回基本类型,这些函数在大多数情况下(但并非总是)返回一个常量表达式.我想根据函数是否为 constexpr 来做不同的事情.我想出了以下方法:

template结构测试{模板静态 std::true_type do_call(int){ return std::true_type();}静态 std::false_type do_call(...){ 返回 std::false_type();}static bool call(){ return do_call(0);}};结构特征{静态 int f(){ 返回 15;}};结构特征{静态 constexpr int f(){ 返回 20;}};int main(){std::cout <<常规:" <<测试<特质>::call()<<std::endl;std::cout <<constexpr:" <<测试<ctrait>::call()<<std::endl;}

额外的 int/... 参数在那里,如果 SFINAE 之后两个函数都可用,第一个被选择重载解析.

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

常规:0常量表达式:1

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

解决方案

注意: 我在这里提出了一个问题,关于 OP 代码是否实际有效.我在下面重写的示例在任何情况下都可以使用.

<小时><块引用>

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

确实如此,虽然默认模板参数可能被认为有点不寻常.我个人更喜欢以下样式,这类似于您(阅读:我)编写特征以检查函数是否存在,仅使用非类型模板参数并省略 decltype:

#include 命名空间细节{模板结构 sfinae_true : std::true_type{};模板sfinae_true<(T::f(), 0)>检查(整数);模板<类>std::false_type 检查(...);}//细节::模板struct has_constexpr_f : decltype(detail::check<T>(0)){};

现场示例.

<小时>

讲解时间~

您的原始代码有效? 因为默认模板参数的实例化点是其函数模板的实例化点,在您的情况下,在 main 中,所以不能更早地替换它.

§14.6.4.1 [temp.point] p2

<块引用>

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

在那之后,这只是通常的 SFINAE 规则.

<小时>

? 至少我是这么认为的,它在标准中并不完全明确.

I'm working on upgrading some C++ code to take advantage of the new functionality in C++11. I have a trait class with a few functions returning fundamental types which would most of the time, but not always, return a constant expression. I would like to do different things based on whether the function is constexpr or not. I came up with the following approach:

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;
}

The extra int/... parameter is there so that if both functions are available after SFINAE, the first one gets chosen by overloading resolution.

Compiling and running this with Clang 3.2 shows:

regular: 0
constexpr: 1

So this appears to work, but I would like to know if the code is legal C++11. Specially since it's my understanding that the rules for SFINAE have changed.

解决方案

NOTE: I opened a question here about whether OPs code is actually valid. My rewritten example below will work in any case.


but I would like to know if the code is legal C++11

It is, although the default template argument may be considered a bit unusual. I personally like the following style better, which is similar to how you (read: I) write a trait to check for a function's existence, just using a non-type template parameter and leaving out the 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.


Explanation time~

Your original code works? because a default template argument's point of instantiation is the point of instantiation of its function template, meaning, in your case, in main, so it can't be substituted earlier than that.

§14.6.4.1 [temp.point] p2

If a function template [...] is called in a way which uses the definition of a default argument of that function template [...], the point of instantiation of the default argument is the point of instantiation of the function template [...].

After that, it's just usual SFINAE rules.


? Atleast I think so, it's not entirely clear in the standard.

相关文章