Závada LaCroix
Závada LaCroix

Reputation: 143

decltype throwing errors on template methods that aren't generating code

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

Answers (2)

Luc Danton
Luc Danton

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

BЈовић
BЈовић

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

Related Questions