Jason R Jason R - 2 months ago 9
C++ Question

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

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.

Answer

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());
Comments