Ambiguous overload due to reference collapsing

119 views Asked by At

I frequently use the template class shown in the code example to make up new types that prevent me from passing wrong arguments, arguments in wrong order and to document the meaning of the arguments. See number (2) in the code example.

But there is one thing that is a little bit of a nuisance: I can´t use this class for reference types (see number (3) in the code example). Of course that doesn´t work, since reference collapsing is making my to constructors ambiguous, thats why I disabled the T&& constructor (see number (1) in the code example) to make the example work. But that is – I hope – only the second best solution?

Question: Is there a way to use the StrongType class, keeping both constructors in case of a non reference types, without creating a special class like StrongTypeRef only for reference types?

The example compiles as it is. To see the error please enable number (1) by uncommenting it.

Thx in advance!

#include <iostream>

template <typename T, typename Parameter>
class StrongType
{
public:
    explicit StrongType(T const& value) : value_(value) {}
    // (1) explicit StrongType(T&& value) : value_(value) {}
    T& get() { return value_; }
    T const& get() const {return value_; }
private:
    T value_;
};

// (2) works great
using Iteration = StrongType<int, struct IterationType>;

// (3) Not o.k.
using IterationRef = StrongType<int&, struct IterationRefType>;

void do_something1(Iteration k)
{
    std::cout << k.get();
}

void do_something2(IterationRef k)
{
    k.get() = 42;
}

int main(int argc, const char * argv[]) {
    Iteration it1(5);
    do_something1(it1);

    int refed = 0;
    IterationRef it2(refed);
    do_something2(it2);
    return 0;
}
2

There are 2 answers

0
Nir Friedman On BEST ANSWER

If you use perfect forwarding, then it works:

template <typename T, typename Parameter>
class StrongType
{
public:
    template <class U>
    explicit StrongType(U&& value) : value_(std::forward<U>(value)) {}
    T& get() { return value_; }
    T const& get() const {return value_; }
private:
    T value_;
};

Live example: http://coliru.stacked-crooked.com/a/a78cb4617961f3c9

0
Sam Varshavchik On

One possible solution is to simply specialize it. Just add a specialization without the ambiguous constructor:

template <typename T, typename Parameter>
class StrongType<T &, Parameter>
{
public:
    explicit StrongType(T const& value) : value_(value) {}
    T& get() { return value_; }
    T const& get() const {return value_; }
private:
    T value_;
};

This will fix the compilation error, without formally creating and having to use a different template based on the template parameter. This is exactly what specializations are for. However, this may or may not address all your problems. For example, the specialization's value_ class member will not be a reference. This may or may not be what you want to happen. Tweak as necessary.

But your inquiry is limited to addressing the compilation error, so that's that. This kind of a brute force solution is fine for small, simple classes. If the basic template is more complex, a plain specialization will, of course, create some code bloat. Some refactoring will be needed, in that case.