Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function overload for string literals lvalue and rvalue reference

The function test below is overloaded for lvalue empty strings, lvalue non-empty strings and rvalue strings. I tried to compile with Clang and GCC but in both case I do not have the result I expected.

#include <iostream>

void test(const char (&)[1]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <unsigned long int N>
void test(const char (&)[N]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }

void test(char*&&){ std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main(){
    char str1[] = "";
    char str2[] = "test";
    test("");
    test("test");
    test(str1);
    test(str2);
}

Output with clang version 6.0.0-1ubuntu2:

clang++ test.cpp -o test.out && ./test.out
void test(const char (&)[1])
void test(const char (&)[N]) [N = 5]
void test(char *&&)
void test(char *&&)

Output with g++ (MinGW.org GCC-8.2.0-3):

g++ test.cpp -o test.exe && test.exe
test.cpp: In function 'int main()':
test.cpp:15:11: error: call of overloaded 'test(char [1])' is ambiguous
  test(str1);
           ^
test.cpp:3:6: note: candidate: 'void test(const char (&)[1])'
 void test(const char (&)[1]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
      ^~~~
test.cpp:6:6: note: candidate: 'void test(const char (&)[N]) [with long unsigned int N = 1]'
 void test(const char (&)[N]){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
      ^~~~
test.cpp:8:6: note: candidate: 'void test(char*&&)'
 void test(char*&&){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
      ^~~~

My questions are:

  1. Which compiler is correct?
  2. With Clang, why test(str1) and test(str2) choose the rvalue overload while they are lvalues?
  3. With GCC, why the call test(str1) is ambiguous?
  4. Is there a standard rule for this situation?
  5. How to fix the two last calls?

Thank you.

like image 766
Baptistou Avatar asked Sep 27 '19 10:09

Baptistou


2 Answers

  1. Which compiler is correct ?

GCC is correct.

  1. With clang, why str1 and str2 choose the rvalue overload while they are lvalues ?

Clang is wrong on test(str1);, it should be ambiguous. For test(str2);, str2 could convert to pointer implicitly, i.e. the array-to-pointer decay. The converted char* is an rvalue. For the same reason as #3, the implicit conversion sequences have the same ranking, then non-template function is prefered; test(char*&&) is selected.

  1. With gcc, why call with str1 is ambiguous ?

For test(const char (&)[1]) to be called, qualification conversion from char[1] to const char[1] is required; for test(char*&&) to be called, array-to-pointer conversion is required. Both are qualified as exact match and have the same ranking.

  1. Is there a standard rule for this situation ?

See the ranking of implicit conversion sequences in overload resolution, and implicit conversions.

  1. How to fix the two last calls ?

It depends on your intent.

like image 192
songyuanyao Avatar answered Nov 08 '22 19:11

songyuanyao


String literals are not rvalues. (→)

  1. How to fix the two last calls?

You can disambiguate everything with template specializations:

#include <iostream>

template<typename C, std::size_t N>
void test(const C (&)[N]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C>
void test(const C (&)[1]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C, std::size_t N>
void test(const C (&&)[N]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C>
void test(const C (&&)[1]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C, std::size_t N>
void test(C (&)[N]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C>
void test(C (&)[1]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C, std::size_t N>
void test(C (&&)[N]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }
template<typename C>
void test(C (&&)[1]) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main(){
    char str1[] = "";
    char str2[] = "test";
    test("");
    test("test");
    test(str1);
    test(str2);
    test(std::move(str1));
    test(std::move(str2));
    const char str3[] = "";
    const char str4[] = "test";
    test(std::move(str3));
    test(std::move(str4));
}

gives

void test(const C (&)[1]) [with C = char]
void test(const C (&)[N]) [with C = char; long unsigned int N = 5]
void test(C (&)[1]) [with C = char]
void test(C (&)[N]) [with C = char; long unsigned int N = 5]
void test(C (&&)[1]) [with C = char]
void test(C (&&)[N]) [with C = char; long unsigned int N = 5]
void test(const C (&&)[1]) [with C = char]
void test(const C (&&)[N]) [with C = char; long unsigned int N = 5]

like image 22
Darklighter Avatar answered Nov 08 '22 21:11

Darklighter