Различное поведение компилятора для выражения: auto p {make_pointer()};

Какое правильное поведение для следующей программы?

// example.cpp

#include <iostream>
#include <memory>

struct Foo {
  void Bar() const {
    std::cout << "Foo::Bar()" << std::endl;
  }
};

std::shared_ptr<Foo> MakeFoo() {
  return std::make_shared<Foo>();
}

int main() {
  auto p { MakeFoo() };
  p->Bar();  
}

Когда я скомпилирую его на своей рабочей станции Linux RHEL 6.6, я получаю следующие результаты:

$ g++ -v
gcc version 5.1.0 (GCC)
$ g++ example.cpp -std=c++14 -Wall -Wextra -pedantic
$ ./a.out
Foo::Bar()

но

$ clang++ -v
clang version 3.6.0 (trunk 217965)
$ clang++ example.cpp -std=c++14 -Wall -Wextra -pedantic
example.cpp:16:4: error: member reference type 'std::initializer_list<std::shared_ptr<Foo> >' is not a pointer; maybe you meant to use '.'?
      p->Bar();
      ~^~
example.cpp:16:6: error: no member named 'Bar' in 'std::initializer_list<std::shared_ptr<Foo> >'
      p->Bar();
      ~  ^
    2 errors generated.

и

$ icpc -v
icpc version 15.0.3 (gcc version 5.1.0 compatibility)
$ icpc example.cpp -std=c++14 -Wall -Wextra -pedantic
example.cpp(16): error: expression must have pointer type
    p->Bar();
    ^
compilation aborted for example.cpp (code 2)

Ответы

Ответ 1

<В > Тл; ДР

Это поведение зависит от предложения и рабочей группы по вопросам эволюции. Существует некоторая двусмысленность относительно того, считается ли это дефектом С++ 14 или предложением С++ 1z. Если это окажется дефектом С++ 14, то поведение gcc корректно для С++ 14. С другой стороны, если это действительно предложение С++ 1z, то clang и icpc демонстрируют правильное поведение.

Подробнее

Похоже, этот случай покрывается N3681, в котором говорится:

Автоматические и сжатые инициализаторы вызывают проблему обучаемости; мы хотим научить людей использовать единую инициализацию, но нам нужно особенно сказать программистам, чтобы избежать брекетов с авто. В С++ 14 мы теперь есть больше случаев, когда авто и фигурные скобки проблематичны; тип возврата вычет для функций частично устраняет проблему, так как возвращение скопированный список не будет работать, поскольку это не выражение. Однако, возвращаясь автоматически возвращается переменная auto, инициализированная из привязанного инициализатора initializer_list, приглашая undefined поведение. Lambda init проблемы с захватом имеют одинаковую проблему. В настоящем документе предлагается изменить автоматически инициализируется автоматически, чтобы не выводить список инициализатора, и ban brace-initialized auto для случаев, когда бит-инициализатор имеет более одного элемента.

и предоставляет следующие примеры:

auto x = foo(); // copy-initialization
auto x{foo}; // direct-initialization, initializes an initializer_list
int x = foo(); // copy-initialization
int x{foo}; // direct-initialization

Итак, я думаю, что теперь clang корректен, последняя версия clang предоставляет это предупреждение:

предупреждение: прямая инициализация списка переменной с выведенным типом изменит значение в будущей версии Clang; вставьте '=' в избегать изменений в поведении [-Wfuture-compat]

Из EWG issue 161, что N3922 был принят для этого.

Как отмечает Praetorian, предложение рекомендует, чтобы это был дефект С++ 14:

Направление от EWG состоит в том, что мы рассматриваем этот дефект в С++ 14.

но clang Замечания о статусе реализации С++ 1z это как предложение С++ 1z, которое не реализовано.

Итак, если это дефект С++ 14, это сделает gcc правильным, но мне непонятно, действительно ли это дефект или предложение.

T.C. указывает здесь комментарий, что кажется, что разработчики clang back-port this. Этого не произошло, и непонятно почему.