Подтвердить что ты не робот

Возможная регрессия в g++ 6.1.0

Следующий код

#include <string>
#include <map>
#include <cassert>

    struct       AV {
        explicit AV(std::string const&) {}
    };

#if 1
    static void check_cache_item(
        std::map<std::string, std::string> const& items) // FIXME remove
    {
        assert(!items.empty());
    }
#endif
    static void check_cache_item(
        std::map<std::string, AV> const& items)
    {
        assert(!items.empty());
    }


int main()
{
    check_cache_item({ { "id", "0" }, { "pk", "#0" } });
    check_cache_item({ { "id", "0" }, { "pk", "#1" } });
    check_cache_item({ { "id", AV{"0"} }, { "pk", AV{"#1"} } });
}

принимается g++ 4.8.4, g++ 5.3.0, clang++ 3.9.0; но g++ 6.1.0 дает ошибку:

cci.cc: In function ‘int main()’:
cci.cc:25:55: error: call of overloaded ‘check_cache_item(<brace-enclosed initializer list>)’ is ambiguous
     check_cache_item({ { "id", "0" }, { "pk", "#0" } });
                                                       ^
cci.cc:10:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&)
     static void check_cache_item(
                 ^~~~~~~~~~~~~~~~
cci.cc:16:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, AV>&)
     static void check_cache_item(
                 ^~~~~~~~~~~~~~~~
cci.cc:26:55: error: call of overloaded ‘check_cache_item(<brace-enclosed initializer list>)’ is ambiguous
     check_cache_item({ { "id", "0" }, { "pk", "#1" } });
                                                       ^
cci.cc:10:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&)
     static void check_cache_item(
                 ^~~~~~~~~~~~~~~~
cci.cc:16:17: note: candidate: void check_cache_item(const std::map<std::__cxx11::basic_string<char>, AV>&)
     static void check_cache_item(
                 ^~~~~~~~~~~~~~~~
cci.cc: At global scope:
cci.cc:10:17: warning: ‘void check_cache_item(const std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&)’ defined but not used [-Wunused-function]
     static void check_cache_item(
                 ^~~~~~~~~~~~~~~~

Если я #ifdef из первого конструктора, то каждый компилятор выдает ошибку (правильно, потому что конструктор AV явный).

Является ли это регрессией в g++ 6.1.0?

4b9b3361

Ответ 1

Это удивительный и несколько неудачный аспект Стандарта (я бы зашел так далеко, чтобы назвать его дефектом); это результат столкновения правил разрешения перегрузки для инициализации списка копий ([over.match.list], как подтверждено в CWG 1228) и конструктор пересылки элементов pair (согласно n4387).

gcc ( >= 6.1.0) правильно отклонить вашу программу; clang неверно принять его. Более ранние версии gcc принимают вашу программу, потому что они еще не реализовали n4387; clang принимает вашу программу, потому что исключает явные конструкторы из соображений для разрешения перегрузки для инициализации списка копий, что нарушает [over.match.list] в соответствии со стандартом (Вызов явного конструктора со списком с фиксированным набором: неоднозначным или нет?)


Если мы устраняем посторонние аспекты вашей программы, это сводится к простому вопросу о разрешении перегрузки:

struct A { explicit A(int, int); };
struct B { B(int, int); };

void f(A);
void f(B);

int main() { f({0, 0}); }

Здесь A стоит за pair<std::string const, AV>, а B стоит за pair<string const, string>. Конструктор A является явным, поскольку шаг n4387 включает явный конструктор AV; но за CWG 1228 правила для инициализации списка копий:

[...] включают все конструкторы, но указывают, что программа плохо сформирована, если явный конструктор выбран с помощью разрешения перегрузки. [...]

[over.match.list]:

[...] В инициализации списка копий, если выбран конструктор explicit, инициализация плохо сформирована. [Примечание. Это отличается от других ситуаций ([over.match.ctor], [over.match.copy]), где рассматриваются только конструкторы преобразования для инициализации копии. Это ограничение применяется только в том случае, если эта инициализация является частью окончательного результата разрешения перегрузки. - конечная нота]

Таким образом, ваша программа правильно рассмотрена (по стандарту, как она есть в настоящее время), чтобы быть неоднозначной.

Дальнейшее чтение: Что может пойти не так, если инициализация списка экземпляров разрешена явным конструкторам?