【问题标题】:C++: template to check if expression compilesC++:检查表达式是否编译的模板
【发布时间】:2015-03-04 15:30:51
【问题描述】:

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

小例子:

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

这可能吗?

【问题讨论】:

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


【解决方案1】:

orElse&lt;v.get(),0&gt;() 的意图已经够清楚了,但是如果这样的事情存在的话, 它必须是以下之一:

调用阵容

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

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

对于任何不明白这一点的人, 原因很简单:Invocation Lineup 中没有任何函数调用 如果没有 V::get 这样的成员,将编译。没有绕过去 那个,以及调用的函数可能是一个实例的事实 Function Template Lineup 中的函数模板没有任何区别。 如果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;
}

在复杂的返回类型中有“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&lt;Args&gt;(args)...) 不编译。但如果它编译了,T::get 的返回类型是 R 以外的东西,那么 std::enable_if_t 类型说明符不 编译。仅当成员函数存在并且具有所需的返回类型R R(T::get) exists 案例可以实例化吗?否则 catch-all R(T::get) 不存在案例被选中。

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

您可以对任何其他R foo(V &amp; v,Args...) 和 存在或不存在R(V::foo)(Args...)。 如果R 不是默认可构造的,或者如果您想要默认的RR(V::foo) 不存在时返回 R(),然后定义一个函数detail::fallback(或其他)返回 所需的默认 R 并指定它而不是 detail::default_ctor

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

【讨论】:

  • 非常好的答案,准确地解释了可能性和不可能性。
【解决方案2】:

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

【讨论】:

  • 另外,您的帖子可能与以下内容重复:stackoverflow.com/questions/257288/…
  • “表达式编译”和“成员函数存在”根本不一样
  • 这个成员检测器是一个好的开始。我只是认为一个通用的解决方案是可能的。
  • @sp2danny 啊,是的,我的错。我想问题是为什么表达式不能正确编译呢?问题是否可以简化为成员检测?即,评估特定成员是否存在的代码然后评估表达式,否则评估一些默认表达式。我想我也会从这个问题的扩展中受益。
猜你喜欢
  • 2023-03-17
  • 1970-01-01
  • 2011-01-08
  • 1970-01-01
  • 2012-05-14
  • 1970-01-01
  • 2015-08-24
  • 1970-01-01
  • 2014-03-31
相关资源
最近更新 更多