Reputation: 143
I'm trying to create a sort of wrapper class that forwards all operators to its contained object, to try and make it able to "pretend" to be the contained object. The code I'd like to write looks something like this (simplified):
template<typename T>
class Wrapper
{
private:
T val;
public:
Wrapper(T value) : val(value) {}
auto operator++() -> decltype(++this->val)
{
return ++this->val;
}
};
This works fine with an int
, but if I try to pass a std::string
into it, I get the error cannot increment value of type 'std::basic_string<char>'
.
I also tried using declval here, but that only made things worse, as not only did it still throw errors on std::string
, it also threw them on int
in that case due to int
not being a class.
Now, in normal situations, this function wouldn't be generated at all because I'm not calling it. But, for whatever reason, the decltype is still being processed on this function even though it isn't being generated at all. (If I remove the decltype and change the return type to void, I can compile with std::string
with no problem.)
So my question is: is there any way I can get around this? Maybe some crazy trick using SFINAE? Or, is it possible this is improper behavior for the compiler in the first place, since the function isn't generating code?
EDIT: Solution, modified somewhat from the solution suggested by 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;
}
};
This will work with both simple and class types, and for class types, will also work in situations where the return type of operator++ is not R (which is probably very rare for operator++, but worth taking into account for the sake of maximum compatibility.)
Upvotes: 2
Views: 346
Reputation: 35449
You indeed want SFINAE. operator++
needs to be made a function template for that, and a good trick is to use a default argument for the template parameter to turn T
into a dependent type (this is necessary for SFINAE to apply).
template<typename U = T>
auto operator++() -> decltype(++std::declval<U&>())
{
return ++this->val;
}
As you may notice however, we lose the convenience of using the member directly, and we need a bit of thinking to figure out what exactly we should feed to std::declval
to get the value category and cv-qualifiers right.
Upvotes: 1
Reputation: 64223
is there any way I can get around this?
You can use boost::has_pre_increment
and 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);
}
is it possible this is improper behavior for the compiler in the first place, since the function isn't generating code?
No. The compiler issues proper diagnostic. The std::string
really has no prefix increment operator. [temp.deduct] 7 and 8 are clear about this :
7:
The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. [ Note: The equivalent substitution in exception specifications is done only when the function is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. — end note ]
8:
If a substitution results in an invalid type or expression, type deduction fails. ...
Upvotes: 2