G ++ 7.1.0 и далее поддерживает гарантированное разрешение копирования в этом конструкторе, но Clang ++ 4.0 и выше не поддерживает - PullRequest
1 голос
/ 14 октября 2019

Я пытался создать класс с членом, который является std :: array типа не для копирования, который мне нужно инициализировать в конструкторе. Я думал, обращаясь к ответам на этот вопрос , что будет работать следующее:

#include <array>
#include <utility>

class Foo {
public:
    Foo() {}
    Foo(const Foo& rhs) = delete;   
    Foo(Foo&& rhs) = delete;
};

template<size_t BufferSize>
class FooBuffer {
public:

    template<size_t... Is>
    FooBuffer(std::index_sequence<Is...>)
        : _buffer({{(static_cast<void>(Is), Foo{})...}})
    {
    }

    FooBuffer() : FooBuffer(std::make_index_sequence<BufferSize>{}) {}

private:    

    std::array<Foo,BufferSize> _buffer;

};

using namespace std;
int main(int, char **) {
    FooBuffer<10> foo;
}

И так, начиная с GCC версии 7.1.0 и далее на C ++Флаги 17 или C ++ 17 (GNU) в соответствии с Wandbox:

ссылка на рабочую компиляцию в GCC 7.1.0

Однако, хотя гарантированная поддержка elision копиив списке для Clang ++ начиная с версии 4 и выше, я не могу найти версию, которая принимает вышеуказанный код, вплоть до версии 9.0 или текущей HEAD:

ссылка на ошибку компилятора в Clang

Получаемая ошибка относится к невозможности копирования массива _buffer:

prog.cc:18:5: error: call to implicitly-deleted copy constructor of 'std::array<Foo, 10UL>'
                : _buffer({{(static_cast<void>(Is), Foo{})...}})
                  ^       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:22:16: note: in instantiation of function template specialization 'FooBuffer<10>::FooBuffer<0, 1, 2, 3, 4, 5, 6, 7, 8, 9>' requested here
        FooBuffer() : FooBuffer(std::make_index_sequence<BufferSize>{}) {}
                      ^
prog.cc:32:16: note: in instantiation of member function 'FooBuffer<10>::FooBuffer' requested here
        FooBuffer<10> foo;
                      ^
/opt/wandbox/clang-head/include/c++/v1/array:143:9: note: copy constructor of 'array<Foo, 10>' is implicitly deleted because field '__elems_' has a deleted copy constructor
    _Tp __elems_[_Size];
        ^
prog.cc:8:2: note: 'Foo' has been explicitly marked deleted here
        Foo(const Foo& rhs) = delete;   
        ^
1 error generated.

Является ли это несогласием в реализации гарантированного исключения копирования между компиляторами? Или я видел здесь признаки на SO, что на гарантированное разрешение копирования нельзя положиться, и он не является обязательным для компилятора. Применимо ли это здесь или только в тех случаях, когда имеется конструктор копирования и GCE не требуется для правильного кода?

Ответы [ 2 ]

3 голосов
/ 14 октября 2019

Когда вы делаете

_buffer({{(static_cast<void>(Is), Foo{})...}})

Часть {(static_cast<void>(Is), Foo{})...} создает braced-init-list для объекта. Внешний {} - это фигурные скобки для создания объекта, на который ссылается braced-init-list . Поскольку ни один из этих списков не имеет типа, компилятор должен перечислить конструкторы _buffer, чтобы узнать, что вызывать. Когда компилятор делает это, все, что найдено, это неявно сгенерированные конструкторы копирования и перемещения. Так как Foo не поддерживает копирование / перемещение, они неявно удаляются, что означает, что нет конструктора, который вы можете вызвать.

Если вы переключитесь на

_buffer{(static_cast<void>(Is), Foo{})...}

, то у вас будет прямая инициализация_buffer и это гарантированно сработает, поскольку значения Foo{} не копируются, а создаются непосредственно на месте.

Вы также можете переключиться на использование

_buffer(std::array<Foo,BufferSize>{{(static_cast<void>(Is), Foo{})...}})

, и это будет работать, потому чтотеперь у вас есть значение типа std::array<Foo,BufferSize>, и вместо копирования оно непосредственно инициализируется в _buffer.


Соответствующий стандарт находится в [dcl.init] / 17

  • Если выражение инициализатора является prvalue, а версия cv-unqualified типа источника является тем же классом, что и класс назначения ,Выражение инициализатора используется для инициализации объекта назначения.

выделение шахты

Поскольку braced-init-list имеетнет типа, это не соответствует вышеуказанной пулеочко, так что не гарантированная копия.

1 голос
/ 14 октября 2019

T(args...) - это прямая инициализация. Для прямой инициализации типа класса исключение копирования гарантируется только в том случае, если аргумент относится к этому типу класса. Но в вашем примере аргумент - это список фигурных скобок.

Неспособность GCC диагностировать проблему является нарушением стандарта.

Это можно исправить, создав явное временноеэто не будет реализовано (как бы странно это ни казалось):

: _buffer(std::array{(static_cast<void>(Is), Foo{})...})

Или просто с помощью инициализации списка:

 _buffer{(static_cast<void>(Is), Foo{})...}
...