Конфликт между конструктором копирования и конструктором пересылки - PullRequest
11 голосов
/ 15 февраля 2012

Эта проблема основана на коде, который работает для меня на GCC-4.6, но не для другого пользователя с CLang-3.0, оба в режиме C ++ 0x.

template <typename T>
struct MyBase
{
//protected:
    T  m;

    template <typename Args...>
    MyBase( Args&& ...x ) : m( std::forward<Args>(x)... ) {}
};

Объект MyBaseможет принимать любой список аргументов конструктора, если T поддерживает эту сигнатуру конструкции.Проблема связана со специальными функциями-членами.

  1. IIUC, шаблон конструктора отменяет автоматически определенный конструктор по умолчанию.Однако, поскольку шаблон может принимать нулевые аргументы, он будет действовать как явно заданный конструктор по умолчанию (если T является конструируемым по умолчанию).
  2. IIUC, определение политики копирования-построения классаигнорирует шаблоны конструктора.Это означает, что в этом случае MyBase получит автоматически определенный конструктор копирования (при условии, что T копируется), который будет направлять T copy-construction.
  3. Применить предыдущий шаг для перемещения-construction также.

Так что, если я передам MyBase<T> const & в качестве единственного аргумента конструктора, какой конструктор вызывается, перенаправляющий или неявный копирующий?

typedef std::vector<Int>  int_vector;
typedef MyBase<int_vector>   VB_type;

int_vector  a{ 1, 3, 5 };
VB_type     b{ a };
VB_type     c{ b };  // which constructor gets called

Проблема моего пользователя заключалась в использовании этого в качестве базового класса.Компилятор жаловался, что его класс не может синтезировать автоматически определенный конструктор копирования, потому что он не может найти совпадения с шаблоном конструктора базового класса.Разве он не должен вызывать MyBase автоматический конструктор копирования для своего собственного автоматического конструктора копирования?Является ли CLang ошибкой при возникновении конфликта?

Ответы [ 3 ]

10 голосов
/ 15 февраля 2012

Я просто в баре с Ричардом Корденом, и между нами мы пришли к выводу, что проблема не имеет ничего общего с переменными или значениями. Неявно сгенерированная конструкция копирования в этом случае принимает MyBase const& в качестве аргумента. Шаблонный конструктор выводит тип аргумента как MyBase&. Это лучшее совпадение, которое вызывается, хотя это не конструктор копирования.

Пример кода, который я использовал для тестирования, таков:

#include <utility>
#include <vector>i

template <typename T>
struct MyBase
{
    template <typename... S> MyBase(S&&... args):
        m(std::forward<S>(args)...)
    {
    }
    T m;
};

struct Derived: MyBase<std::vector<int> >
{
};

int main()
{
    std::vector<int>                vec(3, 1);
    MyBase<std::vector<int> > const fv1{ vec };
    MyBase<std::vector<int> >       fv2{ fv1 };
    MyBase<std::vector<int> >       fv3{ fv2 }; // ERROR!

    Derived d0;
    Derived d1(d0);
}

Мне нужно было отказаться от использования списков инициализаторов, потому что это еще не поддерживается clang. Этот пример компилируется, за исключением инициализации fv3, которая завершается неудачно: конструктор копирования, синтезированный для MyBase<T>, принимает MyBase<T> const& и, таким образом, передача fv2 вызывает конструктор variadic, перенаправляющий объект в базовый класс.

Возможно, я неправильно понял вопрос, но на основании d0 и d1 кажется, что синтезируются как конструктор по умолчанию, так и конструктор копирования. Тем не менее, это с довольно актуальными версиями gcc и clang. То есть, это не объясняет, почему конструктор копирования не синтезируется, потому что он синтезирован.

Чтобы подчеркнуть, что эта проблема не имеет ничего общего с переменными списками аргументов или значениями: в следующем коде показана проблема с вызовом шаблонного конструктора, хотя выглядит так, как будто вызывается конструктор копирования, а конструкторы копирования никогда не являются шаблонами. Это на самом деле несколько удивительное поведение, о котором я точно не знал:

#include <iostream>
struct MyBase
{
    MyBase() {}
    template <typename T> MyBase(T&) { std::cout << "template\n"; }
};

int main()
{
    MyBase f0;
    MyBase f1(const_cast<MyBase const&>(f0));
    MyBase f2(f0);
}

В результате добавление конструктора с переменными параметрами, как в вопросе, к классу, у которого нет других конструкторов, изменяет работу конструкторов копирования поведения! Лично я считаю, что это довольно неудачно. Это фактически означает, что класс MyBase должен быть дополнен также конструкторами копирования и перемещения:

    MyBase(MyBase const&) = default;
    MyBase(MyBase&) = default;
    MyBase(MyBase&&) = default;

К сожалению, это не работает с gcc: он жалуется на конструкторы копий по умолчанию (он утверждает, что конструктор дефолтных копий, принимающий неконстантную ссылку, не может быть определен в определении класса). Clang принимает этот код без каких-либо жалоб. Использование определения конструктора копирования с использованием неконстантной ссылки работает как с gcc, так и clang:

template <typename T> MyBase<T>::MyBase(MyBase<T>&) = default;
1 голос
/ 15 февраля 2012

У меня лично уже давно есть проблема со снимками GCC.У меня были проблемы с выяснением, что происходит (и если это вообще было разрешено), но я пришел к тому же выводу, что и Дитмар Кюль: конструкторы копирования / перемещения все еще здесь, но не всегда предпочтительны из-за механики перегрузкиразрешение.

Я уже некоторое время использую это, чтобы обойти проблему:

// I don't use std::decay on purpose but it shouldn't matter
template<typename T, typename U>
using is_related = std::is_same<
    typename std::remove_cv<typename std::remove_reference<T>::type>::type
    , typename std::remove_cv<typename std::remove_reference<U>::type>::type
>;

template<typename... T>
struct enable_if_unrelated: std::enable_if<true> {};

template<typename T, typename U, typename... Us>
struct enable_if_unrelated
: std::enable_if<!is_related<T, U>::value> {};

Использование его с конструктором, подобным вашему, будет выглядеть так:

template<
    typename... Args
    , typename = typename enable_if_unrelated<MyBase, Args...>::type
>
MyBase(Args&&... args);

Некоторые объяснения в порядке.is_related - это бинарный признак, который проверяет идентичность двух типов независимо от спецификаторов верхнего уровня (const, volatile, &, &&).Идея состоит в том, что конструкторы, которые будут охраняться этой чертой, являются «конвертирующими» конструкторами и не предназначены для работы с параметрами самого типа класса, но только если этот параметр находится на первой позиции.Конструкция с параметрами, например (std::allocator_arg_t, MyBase), будет в порядке.

Теперь я имел обыкновение иметь enable_if_unrelated в качестве бинарной мета-функции, но, поскольку очень удобно, чтобы в нулевом случае работали отлично-перенаправляющие конструкторы с переменными параметрамиЯ также переработал его так, чтобы он принимал любое количество аргументов (хотя он может быть спроектирован так, чтобы принимать хотя бы один аргумент, тип класса конструктора, который мы защищаем).Это означает, что в нашем случае, если конструктор вызывается без аргумента, он не выдается из функции SFINAE.В противном случае вам нужно добавить объявление MyBase() = default;.

Наконец, если конструктор пересылает базу, другой альтернативой является наследование конструктора этой базы (т. Е. using Base::Base;).Это не так в вашем примере.

0 голосов
/ 16 февраля 2012

Я проголосовал за ответ Дитмара, потому что я полностью согласен с ним.Но я хочу поделиться «решением», которое я использовал раньше, чтобы избежать этих проблем:

Я намеренно добавил фиктивный параметр в конструктор переменных:

enum fwd_t {fwd};

template<class T>
class wrapper
{
    T m;
public:
    template<class...Args>
    wrapper(fwd_t, Args&&...args)
    : m(std::forward<Args>(args)...)
    {}
};

:::

int main()
{
    wrapper<std::string> w (fwd,"hello world");
}

Тем более, что конструкторбудет принимать что-либо без этого фиктивного параметра, кажется целесообразным сделать так, чтобы пользовательский код явно выбирал правильный конструктор путем (своего рода) «именования» его.

Это может быть невозможно в вашем случае.Но иногда вы можете сойти с рук.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...