我正在尝试创建一种包装类,它将所有运算符转发到其包含的对象,尝试使其能够“假装”成为包含的对象。我想要写的代码看起来像这样(简化):
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 ++来说可能非常罕见,但值得考虑到最大兼容性。)
答案 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权利