Overload resolution and shared pointers to const - c++

I'm converting a large code to use custom shared pointers instead of raw pointers. I have a problem with overload resolution. Consider this example:
#include <iostream>
struct A {};
struct B : public A {};
void f(const A*)
{
std::cout << "const version\n";
}
void f(A*)
{
std::cout << "non-const version\n";
}
int main(int, char**)
{
B* b;
f(b);
}
This code correctly writes "non-const version" because qualification conversions play a role in ranking of implicit conversion sequences. Now take a look at a version using shared_ptr:
#include <iostream>
#include<memory>
struct A {};
struct B : public A {};
void f(std::shared_ptr<const A>)
{
std::cout << "const version\n";
}
void f(std::shared_ptr<A>)
{
std::cout << "non-const version\n";
}
int main(int, char**)
{
std::shared_ptr<B> b;
f(b);
}
This code doesn't compile because the function call is ambiguous.
I understand that user-defined deduction-guide would be a solution but it still doesn't exist in Visual Studio.
I'm converting the code using regexp because there are thousands of such calls. The regexps cannot distinguish calls that match the const version from those that match the non-const version. Is it possible to take a finer control over the overload resolution when using shared pointers, and avoid having to change each call manually? Of course I could .get() the raw pointer and use it in the call but I want to eliminate the raw pointers altogether.

You could introduce additional overloads to do the delagation for you:
template <class T>
void f(std::shared_ptr<T> a)
{
f(std::static_pointer_cast<A>(a));
}
template <class T>
void f(std::shared_ptr<const T> a)
{
f(std::static_pointer_cast<const A>(a));
}
You can potentially also use std::enable_if to restrict the first overload to non-const Ts, and/or restrict both overloads to Ts derived from A.
How this works:
You have a std::shared_ptr<X> for some X which is neither A nor const A (it's either B or const B). Without my template overloads, the compiler has to choose to convert this std::shared_ptr<X> to either std::shared_ptr<A> or std::shared_ptr<const A>. Both are equally good conversions rank-wise (both are a user-defined conversion), so there's an ambiguity.
With the template overloads added, there are four parameter types to choose from (let's analyse the X = const B case):
std::shared_ptr<A>
std::shared_ptr<const A>
std::shared_ptr<const B> instantiated from the first template, with T = const B.
std::shared_ptr<const B> instatiated from the second template, with T = B.
Clearly types 3 and 4 are better than 1 and 2, since they require no conversion at all. One of them will therefore be chosen.
The types 3 and 4 are identical by themselves, but with overload resolution of templates, additional rules come in. Namely, a template which is "more specialised" (more of the non-template signature matches) is preferred over one less specialised. Since overload 4 had const in the non-template part of the signature (outside of T), it's more specialised and is therefore chosen.
There's no rule that says "templates are better." In fact, it's the opposite: when a template and a non-template have the same cost, the non-template is preferred. The trick here is that the template(s) have lesser cost (no conversion required) than the non-template (user-defined conversion required).

Tag dispatching can solve the issue.
It follows a minimal, working example:
#include <iostream>
#include<memory>
struct A {};
struct B : public A {};
void f(const A*, std::shared_ptr<const A>)
{
std::cout << "const version\n";
}
void f(A*, std::shared_ptr<A>)
{
std::cout << "non-const version\n";
}
template<typename T>
void f(std::shared_ptr<T> ptr)
{
f(ptr.get(), ptr);
}
int main(int, char**)
{
std::shared_ptr<B> b;
f(b);
}
As you can see, you already have what you need to create your tag: the stored pointer.
Anyway, you don't have to get it and pass it around at the call point. Instead, by using an intermediate function template, you can use it as a type to dispatch your calls internally. You don't even have to name the parameter if you don't want to use it.

The reason for the ambiguity is that both std::shared_ptr<A> and std::shared_ptr<const A> can be constructed from std::shared_ptr<B>, due to the converting constructor template. See [util.smartptr.shared.const]:
shared_ptr(const shared_ptr& r) noexcept;
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
18   Remarks: The second constructor shall not participate in overload
resolution unless Y* is compatible with T*.
Thus both overloads have the exact same rank, specifically a user-defined conversion, which leads to the ambiguity during overload resolution.
As a workaround we just need an overload for non-const types:
template <class U, std::enable_if_t<!std::is_const_v<U> && std::is_convertible_v<U*, A*>, int> = 0>
void f(std::shared_ptr<U> a) {
f(std::static_pointer_cast<A>(a));
}
With that we hide the user-defined conversion, making this overload a better match.

Related

C++ overload resolution with template

I want to have a function that takes a std::string and another that takes anything that is not a std::string.
So I have:
#include <iostream>
#include <string>
void foo(const std::string& str) {
std::cout << "string version called" << std::endl;
}
template<class T>
void foo(T&& str) {
std::cout << "template version called" << std::endl;
}
int main() {
foo(std::string{"hello"});
return 0;
}
The problem is that the templated version is called instead of the std::string version.
So how can I have a foo function that either takes anything or specifically a std::string?
The templated foo is using forwarding reference, when being passed temporary std::string like std::string{"hello"}, after the deduction the function parameter str would be std::string&&, it's a better match than the non-template foo which taking const std::string&.
You can impose restrictions on the template parameter to make it usable only when being passed non-std::strings. E.g.
template<class T>
std::enable_if_t<!std::is_same_v<std::decay_t<T>, std::string>>
foo(T&& str) {
std::cout << "template version called" << std::endl;
}
LIVE
Your problem is you are taking by forwarding reference. An rvalue binds to std::string&& better than std::string const&, so yourtemplate ks preferred.
Change it to
template<class T>
void foo(T const& str) {
std::cout << "template version called" << std::endl;
}
use typeid()
template <class T>
void foo(T &&str)
{
if (typeid(str) == typeid(string))
{
std::cout << "string version called" << std::endl;
}else{
std::cout << "template version called" << std::endl;
}
}
int main() {
foo(std::string{"hello"});
foo("hello");
return 0;
}
The problem with your code is that you are creating a temporary variable std::string{"Hello"} which is an exact match for T&& (better than std::string const&) and therefore the templated version is chosen. You could either
Specialise the template for std::string
template<typename T>
void foo(T t) {
// Your implementation for other data types
}
// Template specialisation for strings
template <>
void foo<std::string>(std::string str) {
// Your implementation for strings goes here
}
Overload the function and de-activate the template for std::string, std::string const&, std::string&&, ... For the latter you can use the std::decay<T> type trait struct to disable all of these versions at once in combination with std::is_same.
// Function overload for strings
void foo(std::string const& str) {
// Your implementation for strings goes here
}
// Template disabled for strings
template<class T>
typename std::enable_if<!std::is_same<std::decay<T>::type, std::string>::value>::type
foo(T&& t) {
// Your implementation for the other data types
}
You could compare the typeid or even better in C++17 there is a constexpr if available which could be combined with std::is_same<T>
template<class T>
void foo(T&& t) {
if constexpr (std::is_same_v<T, std::decay_t<std::string>>) {
// Your implementation for strings
} else {
// Your implementation for the other data types
}
}
Furthermore if you wanted to make something like
foo("Hello");
work as well (so instead of foo(std::string{"Hello"})) you could take it a step further also excluding any sort of char*
// Gets used for std::string as well as char*
void foo(std::string const& str) {
std::cout << "string version called" << std::endl;
}
// Disable template for any std::string and char*
template<class T>
typename std::enable_if<!std::is_same<typename std::decay<T>::type, std::string>::value &&
!std::is_same<typename std::decay<T>::type, char const *>::value &&
!std::is_same<typename std::decay<T>::type, char *>::value>::type
foo(T&& t) {
std::cout << "template version called" << std::endl;
}
In this case also foo("Hello") will call the overloaded std::string version. Without this any call without std::string{} will go to the templated version! Try it hereC++11 C++17.
Use an explicit specialization rather than an overloaded function.
template<>
void foo<std::string>(const std::string& str) {
...
I think that's right; explicit specializations of free functions was not original to C++98 but added later at some point.
(it's still a pain to get right, as your specialization has to match the actual type that was deduced for T, which may include const and & if those were not present in the argument list; I've used it effectively for plain pass-by-value types like when the special type is int)
In C++20, you could use a requires clause to make the template not apply for std::string. If requires is not available, you can do the same thing using enable_if.
You might also just write one function, but use constexpr if in the body to provide the special case. This prevents the overloading mechanism from getting involved at all, and lets you code the exact rules for determining the special case.
Update: something old and something new
In the original template specification, function templates could not be explicitly specialized and the idea was that you overload functions instead. You see why this doesn't work as intended: the template is always an exact match, even when the specific function would be called (using trivial conversions, adding const, passing by reference) if overloading just non-template functions.
The work-around was to make a dummy class template, holding a static member function. So, if you originally had a function template f and you needed to explicitly specialize it, move the function body into:
template <typename T>
struct C {
void f (const T&) { /* body goes here */ }
};
Now you can write an explicit specialization of C, and thus C::f:
template<>
struct C<std::string> {
static void f (const std::string&) { /* special code goes here */ }
};
and then, to retain compatibility with the existing code, write a new body for the (non-member) f that just calls C<T>::f.
Now, doing that today you would make it even better and use perfect forwarding.
template <typename T>
void f (const T&& param)
{
C<T>::f(std::forward<T>(param));
}
Now, look at how this differs from just being able to explicitly specialize a function. The template argument deduction is done on the wrapper call, and then the determined value of T is used for the class template instantiation, and then the final member function call does not do any deduction but rather will apply conversions as for normal function calls. It doesn't have the "always a perfect match" behavior. The exact form of the argument can vary; e.g. whether you are passing a value or a const reference.
In fact, thanks to perfect forwarding, it preserves the value category of the wrapper's call, and you can actually overload f within one of the explicit specializations! That is, you could have a separate form for rvalues, constants or non-const, etc.
Here, the wrapper function was declared with const which loses the ability to distinguish non-const parameters, but this makes it easy to have template argument deduction not include the const. You could add your own normalization step to transform the actual argument's type into the plain T you wanted, instead. In fact, you can add any metaprogramming logic you want, such as recognising base and derived classes, which is another issue that shows up with overloading and templates.

Implicit conversion operator for templated types not automatically determined

I have a templated class thing with an implicit conversion operator, like follows:
#include <stdio.h>
template <typename T>
struct thing
{
T t;
operator const T&() const
{
return t;
}
};
template <typename T>
struct B
{
T t;
};
void fun(const int&) {
printf("int\n");
}
template <typename T>
void fun(const B<T>&) {
printf("B<T>\n");
}
int main()
{
thing<int> a;
fun(a);
thing<B<int>> b;
fun(b);
return 0;
}
Calling fun(const int&) with a thing<int>, the compiler is able to figure out to invoke the implicit conversion operator in order to pass the const T& (in this case const int&) to fun(const int&).
However, for a thing<B<int>>, the compiler can not figure out that I expect fun(const B<T>&) to be invoked.
How can I help the compiler figuring this out without casting b to const B<int>& explicitely (using static_cast<const B<int>&>(b) for instance)?
My concrete usage scenario is similar to the code provided with the constraints that I am using B with ~10 different types T, i.e. not arbitrary many different Ts. If I have to create ~10 template specializations, so be it. However, I don't exactly know how to best overload struct B in that case. But maybe I am on the wrong track - do simpler/more elegant solutions exist, maybe?
How can I help the compiler figuring this out without casting b to const B<int>&?
You can't. Templates do not do any implicit conversion. They deduce the type of the parameter and that is the type they use.
One thing you can do is add a get function to your wrapper like
template <typename T>
struct thing
{
T t;
operator const T&() const
{
return t;
}
const T& get() const
{
return t;
}
};
and then you can call fun like
fun(b.get());
Template argument deduction doesn't consider implicit conversion.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
Then given fun(b);, the template fun can't be invoked because T can't be deduced.
You can specify the template argument explicitly, then overload resolution and implicit conversion would work fine.
fun<int>(b);
LIVE

Forward a move reference through a function, without writing the function twice

Recently I often encountered the problem, that I had to write a function which takes an input as a const reference. But at some point this function (usually a constructor) calls another function which could use the input as a move reference. For that reason I usually created a copy of the function to allow const reference and move reference, i.e.
#include <iostream>
class A {};
void foo(const A& a) { std::cout << "Reference" << std::endl; }
void foo( A&& a) { std::cout << "Move" << std::endl; }
void bar(const A& a) {
//Other things, which treat "a" as a const reference
foo(std::move(a));
}
void bar(A&& a) {
//Other things, which treat "a" as a const reference
foo(std::move(a));
}
int main() {
A a;
bar(a);
bar(A());
}
However it is obviously pretty ugly to copy bar two times with the only difference being the signature and the std::move. One alternative I know would be to make bar a tempalte function and to use std::forward, but I don't want to do that because it would allow any parameter type to be passed to bar (especially since in my real application bar is an implicit constructor).
So my question is: Is there any other way to forward a move reference through a function without writing it twice?
If you want to accept both rvalues and lvalues in a single function, retaining the possibility to restore the value category of its argument, you can use a forwarding-reference. You can easily restrict the type of arguments passed in utilizing the expression SFINAE technique, which in your case will verify if the call foo(std::forward<T>(a)) is well-formed. If not, that function will be excluded from the set of viable functions during the overload resolution:
Option #1
Hide the expression SFINAE in a trailing return type:
template <typename T>
auto bar(T&& a)
-> decltype(void(foo(std::forward<T>(a))))
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
DEMO 1
Option #2
Hide the expression SFINAE in a template parameters list:
template <typename T,
typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
void bar(T&& a)
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
DEMO 2
The latter approach is especially useful for constructors (which don't specify a return type):
struct Bar
{
template <typename T,
typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
Bar(T&& a)
{
foo(std::forward<T>(a));
}
};
DEMO 3
Perfect forwarding with SFINAE works and keeps the overload set unpolluted. You first have to decide what exactly should be checked, e.g. the type set this template should be invoked for or expressions that should be valid.
Here, both suffice - this code checks the type:
// Helper template:
template <typename T, typename U, typename R=void>
using enable_if_compatible = typename std::enable_if<std::is_same<U,
typename std::remove_cv<
typename std::remove_reference<T>::type>::type>::value, R>::type;
// Possible usage:
template <typename T>
enable_if_compatible<T, A> bar(T&& a)
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
Demo.
The following one depends on the validity of the call to foo and should be more flexible.
template <typename T>
auto bar(T&& a) -> decltype(void(foo(std::forward<T>(a))))
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
Demo.
One option is to add a templated version of bar that takes a pointer to foo and contains all of the common code that is currently in existing implementations of bar.
template<class T>
void bar(T&& a, void (*f)(T&&a))
{
//Other things, which treat "a" as a const reference
f(std::move(a));
}
void bar(const A& a)
{
bar<const A&>(a, foo);
}
void bar(A&& a)
{
bar(std::move(a), foo);
}

conversion operator with template functions

I have a class with a conversion operator to std::string. It works great with everything except with functions receiving std::basic_string<T> (templated on T).
#include <string>
struct A{
operator std::string(){return std::string();}
};
void F(const std::basic_string<char> &){}
template<typename T> void G(const std::basic_string<T> &) {}
int main(){
A a;
F(a); // Works!
G(a); // Error!
return 0; // because otherwise I'll get a lot of comments :)
}
The error I receive is
error: no matching function for call to 'G(A&)'
note: candidate is:
note: template<class T> void G(const std::basic_string<_CharT>&)
Now, I know I can define G as a friend in the struct A and it'll work, but my problem is with a lot of stl functions that already exist and receive std::basic_string<T> (for example, the operator<< printing function, or comparison operators, or many other functions.
I would really like to be able to use A as if it was an std::string. Is there any way to do this?
I would really like to be able to use A as if it was an std::string. Is there any way to do this?
Yes, but are you sure you really want this? The solution is:
struct A : public std::string {
};
but recall that std::string doesn't have a virtual destructor and therefore, cannot be used polymorphically. You have been warned!!!
A str() is a far better solution and allows you to be explicit when you want to pass your A to a function taking a std::basic_string<T>.
The compiler cannot infer that far; you'll either have to explicitly call the cast operator or to explictly specify the template parameter :
G(static_cast<std::string>(a));
G<char>(a);
To understand why the compiler can't do both user-defined conversion and template argument deduction, let's take this example :
template<typename T>
struct Number {
Number(double n) {};
Number(int n) {};
};
struct A{
operator Number<double>(){return Number<double>(1.);}
operator Number<int>(){return Number<int>(1);}
};
template<typename T> void G(Number<T>& number) { }
int main(){
A a;
G(a); // What do I do ?!
return 0;
}
What the compiler should do in that case ?
User defined conversions are not taken into consideration when performing template argument deduction.
Explicit specialization of G will work.
G<char>(a);

C++: overloading does not choose expected method

I have the following code:
#include <iostream>
#include <vector>
using namespace std;
struct A{};
struct B: public A {};
template <typename T>
void foo(const T& obj) { cerr << "Generic case"<< endl;}
void foo(const A& a) {
cerr << "Specific case" << endl;
}
int main() {
vector<int> v;
foo(v);
B b;
foo(b);
A a;
foo(a);
}
Output is
Generic case
Generic case
Specific case
Why is it that foo(const A& a) is not being chosen for the B object ?
Curiously enough, if I removed the templated method and just have the following:
#include <iostream>
#include <vector>
struct A{};
struct B: public A {};
//template <typename T>
//void foo(const T& obj) { cerr << "Generic case"<< endl;}
void foo(const A& a) {
cerr << "Specific case" << endl;
}
int main() {
B b;
foo(b);
A a;
foo(a);
}
The code compiles and the output is:
Specific case
Specific case
Why is the presence of the templated method making such a difference?
Edit: How can I force the compiler to choose the free method for classes derived from A in the presence
of the templated method?
No conversion is necessary for the call to foo(const B&) which the template instantiation yields thus it is the better match.
When a function call is seen by the compiler, every base function template has to be instantiated and is included in the overload set along with every normal function. After that overload resolution is performed. There is also SFINAE, which allows an instantiation of a function template to lead to an error (such a function would not be added to the overload set). Of course, things aren't really that simple, but it should give the general picture.
Regarding your edit: There is only one method to call. What else could there be as output?
Yes, it is a bit surprising but inheritance and template don't mix so well when it come to overload resolution.
The thing is, when evaluating which overload should be selected, the compiler chooses the one that necessitates the least conversions (built-in to built-in, derived-to-base, calls to non-explicit constructors or conversion operators, etc...). The ranking algorithm is actually pretty complex (not all conversions are treated the same...).
Once the overloads are ranked, if the two top-most are ranked the same and one is a template, then the template is discarded. However, if the template ranks higher than the non-template (less conversions, usually), then the template is selected.
In your case:
for std::vector<int> only one overload matches, so it is selected.
for A two overloads match, they rank equally, the template one is discarded.
for B two overloads match, the template rank higher (no derived-to-base conversion required), it is selected.
There are two work-arounds, the simplest is to "fix" the call site:
A const& ba = b;
foo(ba);
The other is to fix the template itself, however this is trickier...
You can hardcode that for classes derived from A this is not the overload you wish for:
template <typename T>
typename std::enable_if<not std::is_base_of<A, T>::value>::type
foo(T const& t) {
std::cerr << "Generic case\n";
}
However this is not so flexible...
Another solution is to define a hook. First we need some metaprogramming utility:
// Utility
template <typename T, typename Result = void>
struct enable: std::enable_if< std::is_same<T, std::true_type>::value > {};
template <typename T, typename Result = void>
struct disable: std::enable_if< not std::is_same<T, std::true_type>::value > {};
And then we define our hook and function:
std::false_type has_specific_foo(...);
template <typename T>
auto foo(T const& t) -> typename disable<decltype(has_specific_foo(t))>::type {
std::cerr << "Generic case\n";
}
And then for each base class we want a specific foo:
std::true_type has_specific_foo(A const&);
In action at ideone.
It is possible in C++03 too, but slightly more cumbersome. The idea is the same though, an ellipsis argument ... has the worst rank, so we can use overload selection on another function to drive the choice of the primary one.
#pmr's answer explains why the templated function is preferred in your example. To force the compiler to pick your overload instead, you can make use of SFINAE to drop the templated function from the overload set. Change the templated foo to
template <typename T>
typename std::enable_if<!std::is_base_of<A, T>::value>::type
foo(const T& obj) { cerr << "Generic case"<< endl;}
Now, if T is A or a class derived from A the templated function's return type is invalid and it will be excluded from overload resolution. enable_if is present in the type_traits header.