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

C++: template to check if expression compiles

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

小例子:

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

这可能吗?

是的,这或多或少是可能的。它被称为 "member detector"。请参阅此 wikibooks link 以了解如何使用宏来完成此操作。实际实现将取决于您使用的是 pre- 还是 post-C++11 以及您使用的编译器。

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

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

对于任何不明白的人, 原因很简单:None Invocation Lineup 中的函数调用 如果没有 V::get 这样的成员,将编译。没有回头路 那个,以及调用的函数可能是一个实例的事实 函数模板阵容中的函数模板没有任何区别。 如果 V::get 不存在,那么任何提到它的代码都不会编译。

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

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

这将 return R(T::foo) 的值,用参数 args... 调用 obj, 如果存在这样的成员函数,否则 return 一些默认值 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;
}

在复杂的return类型中有"compound 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 的 return 类型是 R 以外的东西,那么 std::enable_if_t 类型说明符不会 编译。仅当成员函数存在且具有所需的 return 类型时 R 可以实例化 R(T::get) exists 案例吗?否则 包罗万象 R(T::get) 不存在 案例被选中。

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

您可以对任何其他 R foo(V & v,Args...) 和 存在或不存在 R(V::foo)(Args...)。 如果 R 不是默认构造的,或者如果你想要默认的 R returned when R(V::foo) does not exist to be something different from R(),然后定义一个函数 detail::fallback(或其他)return 所需的默认值 R 并指定它而不是 detail::default_ctor

如果您可以进一步模板参数化模式该多好 以任何可能的 return 容纳 anyT 可能的成员函数 输入 R。但是你需要的额外模板参数会 是 R(T::*)(typename...),它的实例化值必须是 &V::get(或其他),然后模式将 迫使你陷入提及其存在有疑问的事物的致命陷阱。