Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

template or member function selection

Tags:

c++

templates

I've been trying to understand the way C++ selects templates or member functions. Consider the following code sample:

#include <iostream>
#include <string>

struct Test
{
    template<typename X>
    explicit Test( X&& s){ std::cout << "1" << std::endl;}
    
    explicit Test( const std::string& s) { std::cout << "2"<< std::endl; }
    explicit Test( std::string&& s) { std::cout << "3"<<  std::endl; }

};

int main ()
{
    std::string line = "TEST";
    Test test( line );
}

I'm getting "1" printed on console. Why "2" is not selected if it is a non-template that matches the parameter types?

like image 574
SNJ Avatar asked Nov 15 '20 07:11

SNJ


2 Answers

The constructor template's parameter is declared as forwarding reference. When being passed an lvalue such as line, the template parameter X is deduced as std::string&, and after reference collapsing the parameter type is std::string&, i.e. an lvalue-reference to non-const std::string. It's an exact match and wins in overload resolution.

On the other hand, the constructor taking const std::string& requires const-qualifying the argument, and the constructor taking std::string&& can't be used with lvalues.

like image 174
songyuanyao Avatar answered Nov 01 '22 13:11

songyuanyao


The reason why your templated constructor is selected instead of the non-templated ones has already been explained in this other answer.

However, note that you can still rely on SFINAE for disabling your templated constructor for std::string arguments:

#include <type_traits>

template<typename T>
constexpr bool is_string_v = std::is_same_v<std::decay_t<T>, std::string>;

Then, in your Test class, add an unnamed template parameter to your templated constructor:

struct Test
{
    template<typename X, typename = std::enable_if_t<!is_string_v<X>>>
    explicit Test( X&& s){ std::cout << "1" << std::endl;}
    
    explicit Test( const std::string& s) { std::cout << "2"<< std::endl; }
    explicit Test( std::string&& s) { std::cout << "3"<<  std::endl; }
    
};

This way:

int main ()
{
   std::string line;
   Test test(line);    // selects 2

   const std::string cStr;
   Test testStr(cStr); // selects 2

   int i{};
   Test testInt(i);    // selects 1

   float f{};
   Test testFloat(f);  // selects 1
}

With C++20 concepts, you can very likely end up writing something more readable and expressive.

like image 44
ネロク・ゴ Avatar answered Nov 01 '22 12:11

ネロク・ゴ