成员函数中的 decltype(auto) 忽略无效主体,decltype(expr) 失败

decltype(auto) in member function ignores invalid body, decltype(expr) fails

我有一个简单的模板化包装器结构,其成员函数在其模板类型的对象上调用 .error()

template <typename T>
struct Wrapper {
    T t;
    decltype(auto) f() {
        return t.error(); // calls .error()
    }
};

如果我用一个没有 error() 成员函数的类型实例化它,只要我不调用它就可以了。这就是我想要的行为。

Wrapper<int> w; // no problem here
// w.error(); // uncommented causes compilation failure

如果我使用我认为是尾随 return 类型的语义等价物,它会在变量声明中出错

template <typename T>
struct Wrapper {
    T t;
    auto f() -> decltype(t.error()) {
        return t.error();
    }
};

Wrapper<int> w; // error here

我承认这两者在语义上并不等价,但是无论如何可以使用尾随 return 类型(仅限 C++11)来获得前者的行为,而无需专门化整个 class 有某种 HasError tmp 诡计?

版本差异

decltype(auto) f();
auto f() -> decltype(t.error());

是第二个的函数声明可以无效。 Return 函数模板的类型推导发生在实例化 定义 [dcl.spec.auto]/12。尽管我找不到任何有关 class 模板成员函数的 return 类型推导的信息,但我认为它们的行为相似。

隐式实例化 class 模板 Wrapper 会导致实例化 声明 ,但不会实例化 定义 所有(非虚拟)成员函数 [temp.inst]/1。声明 decltype(auto) f(); 有一个非推导的占位符但有效。另一方面,auto f() -> decltype(t.error()); 有一些实例化的无效 return 类型。


C++11 中的一个简单解决方案是推迟 return 类型的确定,例如通过将 f 变成函数模板:

template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() );

不过,该函数的定义让我有点担心:

template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() )
{
    return t.error();
}

对于 Wrapper 的特化,其中 t.error() 无效,上面的 f 是一个无法产生有效特化的函数模板。这可能属于 [temp.res]/8,它表示此类模板格式错误,无需诊断:

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

但是,我怀疑该规则已被引入 allow,但不要求实现检查非实例化模板中的错误。在这种情况下,源代码没有编程错误;该错误将发生在源代码描述的 class 模板的实例化中。所以,我觉得应该没问题。


另一种解决方案是使用后备 return 类型使函数 声明 格式正确,即使 定义 不是(对于所有实例化):

#include <type_traits>

template<typename T> struct type_is { using type = T; };

template <typename T>
struct Wrapper {
    T t;

    template<typename U=T, typename=void>
    struct error_return_type_or_void : type_is<void> {};

    template<typename U>
    struct error_return_type_or_void
        <U, decltype(std::declval<U&>().error(), void())>
    : type_is<decltype(std::declval<U&>().error())> {};

    auto f() -> typename error_return_type_or_void<>::type {
        return t.error();
    }
};

一种方法是标记的 crtp。

  // todo:
  template<class T>
  struct has_error; // true_type if T.error() is valid

  template<class D,class T,bool Test=has_error<T>{}>
  struct do_whatever {
    D* self(){return static_cast<D*>(this);}
    D const* self()const{return static_cast<D const*>(this);}

    auto f()->decltype(self()->t.error()) {
      return self()->t.error();
    }
  };
  template<class D,class T>
  struct do_whatever<D,T,false>{};

如果 T 没有 error(),现在 f() 就不存在了。