Перегрузка функции для строковых литералов lvalue и rvalue

Функция test ниже перегружена для lvalue пустых строк, lvalue непустых строк и rvalue строк. Я пытался скомпилировать с Clang и GCC, но в обоих случаях у меня нет ожидаемого результата.

#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);
}

Вывод clang версии 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 *&&)

Вывод с 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; }
      ^~~~

Мои вопросы:

  1. Какой компилятор правильный?
  2. С помощью Clang, почему test(str1) и test(str2) выбирают перегрузку rvalue, пока они являются lvalues?
  3. Почему в GCC вызов test(str1) неоднозначен?
  4. Есть ли стандартное правило для этой ситуации?
  5. Как исправить два последних звонка?

Спасибо.

Ответы

Ответ 1

  1.    Какой компилятор правильный?

GCC правильно.

  1. С помощью clang, почему str1 и str2 выбирают перегрузку rvalue, в то время как они являются lvalues?

Clang не прав на test(str1);, это должно быть неоднозначно. Для test(str2); str2 может неявно преобразовывать указатель, то есть затухание массива в указатель. Преобразованный char* является значением. По той же причине, что и # 3, последовательности неявного преобразования имеют одинаковое ранжирование, тогда предпочтительна не шаблонная функция; test(char*&&) выбран.

  1. С gcc, почему вызов с str1 неоднозначен?

Для вызова test(const char (&)[1]) требуется преобразование квалификации из char[1] в const char[1]; для вызова test(char*&&) требуется преобразование массива в указатель. Оба квалифицируются как точное совпадение и имеют одинаковый рейтинг.

  1. Есть ли стандартное правило для этой ситуации?

См. ранжирование последовательностей неявного преобразования в разрешении перегрузки и неявных преобразований.

  1. Как исправить два последних звонка?

Это зависит от ваших намерений.

Ответ 2

Строковые литералы не являются значениями. ()

  1. Как исправить два последних звонка?

Вы можете устранить неоднозначность всего с помощью специализаций шаблонов:

#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));
}

дает

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]

Ответ 3

Спасибо @songyuanyao за ваш ответ, теперь я понимаю, почему test(char*&&) выбран в двух последних случаях. Благодаря ответу @Darklighter мне удалось устранить неоднозначность с помощью специализации шаблона при первой перегрузке.

Поэтому я решил свою проблему, например, ниже:

#include <iostream>

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

template <>
void test(const char (&)[1]){
    std::cout << __PRETTY_FUNCTION__ << " //empty literal" << std::endl;
}

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

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

Вывод:

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

g++ test.cpp -o test.exe && test.exe
void test(const char (&)[N]) [with long unsigned int N = 1] //empty literal
void test(const char (&)[N]) [with long unsigned int N = 5] //non-empty literal
void test(char*&&) //string variable
void test(char*&&) //string variable