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.
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>
}
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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With