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

时间:2015-03-04 15:30:51

标签: c++ templates sfinae specialization partial-specialization

在使用SFINAE编写模板专业化时,由于一个小的不存在的成员或函数,您经常需要编写一个全新的特化。我想将这个选择打包成一个小的语句,如orElse<T a,T b>

小例子:

template<typename T> int get(T& v){
    return orElse<v.get(),0>();
}

这可能吗?

2 个答案:

答案 0 :(得分:2)

是的,这或多或少是可能的。它被称为“成员探测器”。有关如何使用宏完成此操作,请参阅此wikibooks link。实际实现将取决于您使用的是C ++之前的版本还是之后的11,以及您正在使用的编译器。

答案 1 :(得分:2)

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

调用阵容

orElse(v,&V::get,0)
orElse<V,&V::get>(v,0)
orElse<V,&V::get,0>(v)

其中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);

如你所知,没有这样的东西可以存在你想要的效果。

对于任何没有得到的人, 原因很简单:调用阵容中没有函数调用 如果没有V::get这样的成员,将编译。没有回合 那个,以及被调用的函数可能是一个实例化的事实 功能模板阵容中的功能模板无论如何都没有区别。 如果V::get不存在,则提及它的任何代码都将无法编译。

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

template<typename T, typename ...Args>
R foo(T && obj, Args &&... args);

将返回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

可以尝试:

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

复杂的返回类型中有“复合SFINAE”:

std::enable_if_t<
        std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
    >::value,R>

如果T::get不存在,那么decltype(obj.get(std::forward<Args>(args)...) 不编译。但是如果它确实编译了,那么T::get的返回类型就是 除R以外的其他内容,则std::enable_if_t类型说明符不会 编译。仅当成员函数存在且具有所需的返回类型R时 可以R(T::get) 存在案例进行实例化。否则 catch-all R(T::get) 不存在案例被选中。

请注意get(aconst)返回0而不是1.这是应该的, 因为无法在const A::get()上调用非const重载A

您可以对任何其他R foo(V & v,Args...)使用相同的模式 存在的或不存在的R(V::foo)(Args...)。 如果R不是默认构造的,或者您希望默认RR(V::foo)不存在时返回与...不同的东西 R(),然后定义返回的函数detail::fallback(或其他) 所需的默认R并指定它而不是detail::default_ctor

你可以进一步模板化 - 模式化这种模式真是太好了 容纳 T的任何可能的成员函数,并提供任何可能的回报 输入R。但是你需要的附加模板参数会 是R(T::*)(typename...),它的实例化值必须是 &V::get(或其他),然后模式会 迫使你陷入致命的陷阱,提到存在存在疑问的东西。