【问题标题】:decltype throwing errors on template methods that aren't generating codedecltype 在不生成代码的模板方法上抛出错误
【发布时间】:2013-02-13 04:43:04
【问题描述】:

我正在尝试创建一种将所有运算符转发到其包含对象的包装类,以尝试使其能够“假装”为包含对象。我喜欢写的代码看起来像这样(简化):

template<typename T>
class Wrapper
{
private:
    T val;
public:
    Wrapper(T value) : val(value) {}
    auto operator++() -> decltype(++this->val)
    {
        return ++this->val;
    }
};

这适用于 int,但如果我尝试将 std::string 传递给它,我会收到错误 cannot increment value of type 'std::basic_string&lt;char&gt;'

我也尝试在这里使用 declval,但这只会让事情变得更糟,因为它不仅仍然在 std::string 上抛出错误,而且在这种情况下它还在 int 上抛出错误,因为 int 不是一个类.

现在,在正常情况下,这个函数根本不会生成,因为我没有调用它。但是,无论出于何种原因,decltype 仍在此函数上进行处理,即使它根本没有生成。 (如果我删除 decltype 并将返回类型更改为 void,我可以使用 std::string 毫无问题地编译。)

所以我的问题是:有什么办法可以解决这个问题吗?也许使用 SFINAE 的一些疯狂技巧?或者,由于函数没有生成代码,这是否可能首先是编译器的不当行为?

编辑:解决方案,根据 BЈовић 建议的解决方案进行了一些修改:

//Class, supports operator++, get its declared return type
template<typename R, bool IsObj = boost::is_class<R>::value, bool hasOp = boost::has_pre_increment<R>::value> struct OpRet
{
    typedef decltype(++std::declval<R>()) Ret;
};
//Not a class, but supports operator++, return type is R (i.e., ++int returns int)
template<typename R> struct OpRet<R, false, true>
{
    typedef R Ret;
};
//Doesn't support operator++, return type is void
template<typename R> struct OpRet<R, true, false>
{
    typedef void Ret;
};
template<typename R> struct OpRet<R, false, false>
{
    typedef void Ret;
};

template<typename T>
class Wrapper
{
private:
    T val;
public:
    Wrapper(T value) : val(value) {}
    auto operator++() -> typename OpRet<T>::Ret
    {
        return ++val;
    }
};

这适用于简单类型和类类型,对于类类型,也适用于 operator++ 的返回类型不是 R 的情况(这对于 operator++ 来说可能非常罕见,但为了最大的兼容性。)

【问题讨论】:

  • 如果它不处理所有的方法声明,它怎么知道它什么时候需要实例化一个方法定义?
  • 我的假设是它不会检查 decltype 的参数,直到它真正开始为该方法生成代码。为什么需要?只是浪费周期获取不会使用的信息。
  • 这个包装的意义何在?为什么不直接公开该成员?
  • 如果让运算符返回void,你会返回什么?
  • @Pubby:也许这段代码是对错误的简单重现。我们鼓励人们不要向我们展示事情有多复杂。

标签: c++ templates c++11 sfinae decltype


【解决方案1】:

有什么办法可以解决这个问题吗?

您可以使用boost::has_pre_increment 和 SFINAE :

#include <string>
#include <boost/type_traits.hpp>


template<typename R,bool hasOp = boost::has_pre_increment<R>::value > struct OpRet
{
  typedef R Ret;
};
template<typename R> struct OpRet<R,false>
{
  typedef void Ret;
};


template<typename T>
class Wrapper
{
private:
    T val;
public:
    Wrapper(T value) : val(value) {}
    auto operator++() -> typename OpRet<T>::Ret
    {
        return ++val;
    }
};

int main()
{
  Wrapper<std::string> a("abc");
  Wrapper<int> b(2);
}

这是否可能是编译器的不当行为,因为函数没有生成代码?

没有。编译器发出正确的诊断。 std::string 确实没有前缀增量运算符。 [temp.deduct] 7 和 8 对此很清楚:

7:

替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。表达式不仅包括常量表达式,例如出现在数组边界或作为非类型模板参数的常量表达式,还包括 sizeof、decltype 和其他允许非常量表达式的上下文中的通用表达式(即非常量表达式)。 [注意:异常规范中的等效替换仅在函数被实例化时进行,此时如果替换导致无效的类型或表达式,则程序是非良构的。 ——尾注]

8:

如果替换导致无效的类型或表达式,则类型推导失败。 ...

【讨论】:

  • 比我希望的要冗长一些,我也希望避免依赖于 boost,但这很有效,所以谢谢。 :)
  • 啊...但这总是返回 T 类型的值。因此,如果出于某种原因,它与实现 operator++ 的类一起使用时,它的返回类型与自身不同,这就是 decltype 表达式的原因。
  • 但是,经过一些修改,这就是我所需要的。我把我的修改放在了问题中。
  • @ZávadaLaCroix 不要在你的问题中回答,而是回答你自己的问题。并接受这一点
  • 但这是你的答案,所以我想感谢你!我只是稍微调整了一下。其中 95% 仍然是您的。
【解决方案2】:

您确实想要 SFINAE。 operator++ 需要为此创建一个函数模板,一个好的技巧是使用模板参数的默认参数将 T 转换为依赖类型(这是 SFINAE 应用所必需的)。

template<typename U = T>
auto operator++() -> decltype(++std::declval<U&>())
{
    return ++this->val;
}

然而,正如您可能注意到的那样,我们失去了直接使用成员的便利性,我们需要思考一下究竟应该向std::declval 提供什么,以正确获取值类别和 cv-qualifiers。

【讨论】:

  • 这适用于类类型,但不适用于简单类型,例如int,因为int 不能用作declval 的模板参数。
  • @ZávadaLaCroix 这根本不是真的。您正在使用的实现可能存在缺陷。在这种情况下,您可以尝试通过声明来替换您自己的:template&lt;typename T&gt; typename std::add_rvalue_reference&lt;T&gt;::type declval() noexcept;
猜你喜欢
  • 1970-01-01
  • 2013-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-23
相关资源
最近更新 更多