Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a canonical way to allow implicit conversion of a non-const template argument type to a const one?

Tags:

c++

c++11

I have a function that takes arguments of a specific template type; a simplified version might look like:

#include <type_traits>

template <typename T>
struct foo
{
    // default constructor
    foo() { }

    // simple copy constructor that can construct a foo<T> from a foo<T>
    // or foo<const T>
    foo(const foo<typename std::remove_const<T>::type> &) { }
};

Functionally, foo behaves similar to a shared_ptr<T>, with some other addon functionality that isn't relevant to this question. The semantics of the function dictate that it prefers to take in a foo<const T>. foo<const T> is implicitly constructible from foo<T>, so I would like to be able to do something like the following:

template <typename T>
void bar(foo<const T> f) { }

int main()
{
    bar(foo<const int>()); // fine
    bar(foo<int>());       // compile error
}

This fails because there are no matching overloads for bar that take a foo<int> (even though a foo<const int> could be implicitly constructed from a foo<int>, the overload resolution in concert with template instantiation seems to be stricter than that.

Is there a canonical way to accomplish this? I know that I could introduce a second overload for bar() that takes a foo<T> and dispatches manually to bar(foo<const T>), but I'd like to avoid the duplication if possible.

like image 394
Jason R Avatar asked Sep 13 '16 18:09

Jason R


2 Answers

Templates don't allow for conversions!

When you write:

template <typename T>
void bar(foo<const T> f) { }

bar accepts a foo<const T>, for any T. It accepts nothing else. It doesn't matter that foo<int> is convertible to foo<const int>, that conversion is never considered. Full stop.

If you want to treat the received f as const, you can conditionally make it const:

// convert this version
template <class T> foo<T const> make_const_f(foo<T> const& rhs) { return {rhs}; }    
// pass this version through
template <class T> foo<T const>& make_const_f(foo<T const>& rhs) { return rhs; }

template <typename T>
void bar(foo<T> f) {
    auto&& const_f = make_const_f(f);

    // if T was const, const_f is a reference to f
    // if T wasn't const, const_f is a new object of type foo<T const>
}
like image 119
Barry Avatar answered Oct 30 '22 04:10

Barry


The reason you code doesn't work is because implicit conversion is apply after template argument deduction. So in that case, foo<int> indeed won't match foo<const T>, and the compiler won't able to deduce what the T is. You can try by yourself to check specify the type directly:

int main()
{
    bar(foo<const int>()); // fine
    bar<int>(foo<int>());  // also fine
}

What you can do is to let the compiler take any type:

template <typename T> // T might be foo<int>
void bar(T f) { }

Or if you want, you can let the compiler deduce the inner T without the const:

template <typename T> // can deduce T as const int
void bar(foo<T> f) { }

If you really want to enforce constness (even in generic code), you might want to add a utility function to your class, something like this:

foo<const T> as_const() const { return *this; }

So when you use a generic function, you can send is a const version of your class:

bar<int>(foo<int>{}.as_const());
like image 43
Guillaume Racicot Avatar answered Oct 30 '22 04:10

Guillaume Racicot