2

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

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

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

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

现在,在正常情况下,根本不会生成这个函数,因为我没有调用它。但是,无论出于何种原因,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++ 可能非常罕见,但为了最大程度的兼容性值得考虑。 )

4

2 回答 2

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:

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

于 2013-02-13T08:18:19.023 回答
1

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

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

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

于 2013-02-13T08:55:43.317 回答