decltype在不生成代码的模板方法上抛出错误

时间:2013-02-13 04:43:04

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

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

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<char>'

我也试过在这里使用declval,但这只会让事情变得更糟,因为它不仅会在std::string上抛出错误,在这种情况下它也会因为{{1}而将它们抛到int上不是一个阶级。

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

所以我的问题是:有什么方法可以解决这个问题吗?也许使用SFINAE的一些疯狂伎俩?或者,这首先是编译器的不正当行为,因为该函数不生成代码?

编辑:解决方案,稍微改进了BЈовић建议的解决方案:

std::string

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

2 个答案:

答案 0 :(得分:2)

  

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

您可以使用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:

  

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

答案 1 :(得分:1)

你确实想要SFINAE。 operator++需要成为一个函数模板,一个好方法是使用模板参数的默认参数将T转换为依赖类型(这是SFINAE应用所必需的)。

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

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