C++:用于检查表达式是否编译的模板

tly*_*tly 6 c++ templates partial-specialization specialization sfinae

在使用SFINAE编写模板专业化时,您经常会因为一个小的不存在的成员或函数而需要编写一个全新的特化.我想把这个选择打包成一个小的声明,比如orElse<T a,T b>.

小例子:

template<typename T> int get(T& v){
    return orElse<v.get(),0>();
}
Run Code Online (Sandbox Code Playgroud)

这可能吗?

Mik*_*han 2

的意图orElse<v.get(),0>()很清楚,但如果这样的事情可能存在,它必须是以下之一:

召唤阵容

orElse(v,&V::get,0)
orElse<V,&V::get>(v,0)
orElse<V,&V::get,0>(v)
Run Code Online (Sandbox Code Playgroud)

其中v的类型为V,因此实例化的函数模板将分别为:

功能模板阵容

template<typename T>
int orElse(T & obj, int(T::pmf*)(), int deflt);

template<typename T, int(T::*)()>
int orElse(T & obj, int deflt);

template<typename T, int(T::*)(), int Default>
int orElse(T & obj);
Run Code Online (Sandbox Code Playgroud)

正如您所理解的,没有这样的东西可以达到您想要的效果。

对于任何不明白这一点的人,原因很简单: 如果没有这样的成员,则调用阵容V::get中的任何函数调用都不会编译。这是无法回避的,而且调用的函数可能是函数模板阵容中函数模板的实例化这一事实没有任何区别。如果V::get不存在,则任何提及它的代码都将无法编译。

然而,你似乎有一个实际的目标,不需要以这种绝望的方式来实现。看起来好像,对于给定的名称foo和给定的类型R,您希望能够只编写一个函数模板:

template<typename T, typename ...Args>
R foo(T && obj, Args &&... args);
Run Code Online (Sandbox Code Playgroud)

R(T::foo)如果这样的成员函数存在,它将返回使用参数调用obj的值args...,否则返回一些默认值R

如果正确的话,可以按照下图实现:

#include <utility>
#include <type_traits>

namespace detail {

template<typename T>

T default_ctor()
{
    return T();
}

// SFINAE `R(T::get)` exists
template<typename T, typename R, R(Default)(), typename ...Args>
auto get_or_default(
    T && obj,
    Args &&... args) ->
    std::enable_if_t<
        std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
    >::value,R>
{
    return obj.get(std::forward<Args>(args)...);
}

// SFINAE `R(T::get)` does not exist
template<typename T, typename R, R(Default)(), typename ...Args>
R get_or_default(...)
{
    return Default();
}

} //namespace detail


// This is your universal `int get(T,Args...)`
template<typename T, typename ...Args>
int get(T && obj, Args &&... args)
{
    return detail::get_or_default<T&,int,detail::default_ctor>
        (obj,std::forward<Args>(args)...);
}

// C++14, trivially adaptable for C++11
Run Code Online (Sandbox Code Playgroud)

可以尝试使用:

#include <iostream>

using namespace std;

struct A
{
    A(){};
    int get() {
        return 1;
    }
    int get(int i) const  {
        return i + i;
    }
};

struct B
{
    double get() {
        return 2.2;
    }
    double get(double d) {
        return d * d;
    }
};

struct C{};

int main()
{
    A const aconst;
    A a;
    B b;
    C c;
    cout << get(aconst) << endl;    // expect 0
    cout << get(a) << endl;         // expect 1 
    cout << get(b) << endl;         // expect 0
    cout << get(c) << endl;         // expect 0
    cout << get(a,1) << endl;       // expect 2
    cout << get(b,2,2) << endl;     // expect 0
    cout << get(c,3) << endl;       // expect 0
    cout << get(A(),2) << endl;     // expect 4
    cout << get(B(),2,2) << endl;   // expect 0
    cout << get(C(),3) << endl;     // expect 0
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

复杂返回类型中存在“复合 SFINAE”:

std::enable_if_t<
        std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
    >::value,R>
Run Code Online (Sandbox Code Playgroud)

如果T::get不存在则不decltype(obj.get(std::forward<Args>(args)...) 编译。但是,如果它确实可以编译,并且 的返回类型T::get不是R,则std::enable_if_t类型说明符不会编译。只有当成员函数存在并且具有所需的返回类型时,existsR情况 才能被实例化。否则,选择不存在包罗万象的情况。R(T::get) R(T::get)

请注意,get(aconst)返回 0 而不是 1。这应该是这样,因为A::get()不能在 const 上调用非常量重载A

您可以将相同的模式用于任何其他R foo(V & v,Args...)存在或不存在的R(V::foo)(Args...)。如果R不可默认构造,或者如果您希望在不存在R时返回的默认值R(V::foo)与 不同 ,则定义一个返回所需默认值的R()函数(或其他)并指定它而不是detail::fallbackRdetail::default_ctor

如果您可以进一步对模式进行模板参数化以适应具有任何可能的返回类型的任何可能的成员函数,那该多好啊。但是您需要的附加模板参数将是,并且它的实例化值必须是 (或其他),然后该模式将迫使您陷入致命的陷阱,即提及其存在受到怀疑的事物。TRR(T::*)(typename...)&V::get