【问题标题】:decltype(auto) in member function ignores invalid body, decltype(expr) fails成员函数中的 decltype(auto) 忽略无效主体,decltype(expr) 失败
【发布时间】:2015-04-06 00:10:05
【问题描述】:

我有一个简单的模板化包装结构,其成员函数在其模板类型的对象上调用 .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

如果我使用我认为与尾随返回类型等效的语义,它会在变量声明中出错

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

Wrapper<int> w; // error here

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

【问题讨论】:

  • @dyp decltype(auto) 在 C++14 中添加。
  • @Brian 啊,我被 C++14 标签误导了。应该仔细阅读,谢谢。
  • 如果t是公共数据成员,为什么不使用免费函数(模板)?
  • @dyp 在真实案例中是私有的

标签: c++ templates c++11 c++14


【解决方案1】:

版本之间的区别

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

是第二个的函数声明可以是无效的。当定义被实例化[dcl.spec.auto]/12时,函数模板的返回类型推导发生。虽然我找不到关于类模板成员函数的返回类型推导的任何信息,但我认为它们的行为相似。

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


C++11 中一个简单的解决方案是推迟返回类型的确定,例如通过将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();
}

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

如果无法为模板生成有效的特化,并且该模板未实例化,则模板格式错误,无法诊断 必填。

但是,我怀疑该规则已被引入 allow,但不是必需的实现来检查非实例化模板中的错误。在这种情况下,源代码中没有编程错误;该错误将发生在源代码描述的类模板的实例化中。因此,我认为应该没问题。


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

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

【讨论】:

  • 应该添加一些错误检查以确保U == T,例如static_assert.
  • 我认为[temp.res]/8不适用于这种情况,因为实例化类模板特化只实例化成员函数模板的声明,所以就检查而言,所有你有的是声明。在这种情况下应用它意味着您比普通成员函数更严格地检查成员函数模板,并且使用 std::vector&lt;DefaultConstructible&gt; 是格式错误的 NDR(模板 insert()assign() 采用非转发迭代器需要 MoveInsertable/ MoveAssignable),这几乎肯定不是委员会的意图。
  • @t.c.也许,但意图不能原谅不允许的代码,除非没有其他选择。我读到它是为了以后允许进行更多检查,但是这样的检查可能不会在 coukd (当前)有效的模板上出错。因此,如果我们最终确保模板始终无效并在未来的迭代中添加诊断,我们只会破坏已经形成错误的代码。
  • @Yakk 关键是类模板的成员函数定义的延迟实例化绝对是一种预期的技术,并且在标准库中的所有地方都使用过。没有理由相信成员函数模板应该被区别对待。
  • @t.c.除了标准中的一个子句说所有函数模板都必须有一个有效的实例化。也许它需要一个“除了 tsmplate 类型的成员”或“如果成员函数模板包含在模板类中,则该类和函数的模板参数必须有某种组合,从而导致合法的实例化”。如果标准有这样的话,那就太好了。但事实并非如此。 :`( (两个引用都不完全正确:我们需要无限递归)
【解决方案2】:

一种方法是标记 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() 不存在。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-18
    • 2019-12-02
    • 2011-07-06
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多