2012-02-15 3 views
9

Эта проблема основана на коде, который работает для меня на 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. Применить предыдущий шаг для перемещения.

Так что если я передаю 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 ошибкой для возникновения конфликта?

+0

Интересный вопрос. Я рекомендую сделать sscce (см. Http://sscce.org/), который работает в GCC и не работает в CLang, чтобы помочь нам лучше понять проблему и воссоздать ее самостоятельно. Также, пожалуйста, сообщите нам точное сообщение об ошибке от CLang. –

+2

На самом деле, версия gcc относительно близко к голове (начиная с 20120202) также не принимает этот код. Кажется, что конструктор пересылки подбирается даже для копии. Я не совсем понимаю, почему. Он не связан с «равномерным синтаксисом инициализации»: даже при использовании скобок для последнего объявления он не компилируется. –

+0

Мой код - это то, что я написал десять лет назад для Boost и обновил для C++ 11 на прошлой неделе. Я не знаю, как работать с системой Boost Trac, но вы можете посмотреть [мои изменения] (https://svn.boost.org/trac/boost/changeset/76982) и [текущее состояние] (http : //svn.boost.org/svn/boost/trunk/boost/utility/base_from_member.hpp) файла (в редакции 77031 в этом письме). – CTMacUser

ответ

9

Я просто в баре с Ричардом Корденом, и между нами мы пришли к выводу, что проблема не имеет ничего общего с переменными или rvalues. Неявно созданная копия в этом случае принимает аргумент 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); 
} 

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

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

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

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

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

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

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

template <typename T> MyBase<T>::MyBase(MyBase<T>&) = default; 
+0

@MooingDuck: я не мог воспроизвести проблему с использованием соответствующей базы класс (см. мое обновление для ответа). –

+0

Да, я использовал оригинальные имена и не обновлял все места. Надеюсь, теперь все исправлено. –

1

Я лично была проблема с ССАГПЗ снимков в течение довольно продолжительного времени. Мне было трудно понять, что происходит (и если это разрешено вообще), но я пришел к аналогичному выводу, как Дитмар Кюль: конструкторы копирования/перемещения все еще здесь, но не всегда предпочтительны благодаря механике перегрузки разрешающая способность.

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

// 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'd. В противном случае вам нужно будет добавить объявление MyBase() = default;.

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

0

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

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

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

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

Возможно, это будет невозможно в вашем случае. Но иногда вам это удается.

Смежные вопросы