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

Как я могу препятствовать тому, чтобы конструктор Variadic был предпочтительнее для конструктора копирования?

У меня есть шаблон Foo, которому принадлежит T, и я бы хотел, чтобы у него был конструктор Variadic, который переводит свои аргументы в конструктор T:

template<typename T>
struct Foo {

    Foo()
        : t() {}

    Foo(const Foo& other)
        : t(other.t) {}

    template<typename ...Args>
    Foo(Args&&... args)
        : t(std::forward<Args>(args)...) {}

    T t;
};

Однако это не позволяет скопировать Foo:

int main(int argc, char* argv[]) {
    Foo<std::shared_ptr<int>> x(new int(42));
    decltype(x) copy_of_x(x);  // FAILS TO COMPILE
    return EXIT_SUCCESS;
}

потому что, согласно этот ответ, неконстантность аргумента приводит к тому, что конструктор Variadic лучше сочетается. Я не хочу принуждать своих абонентов использовать const_cast по понятным причинам.

Одним из возможных решений, которое я нашел, было написать "конструктор копирования" для Foo, который принимает неконстантный Foo и использует переадресацию конструктора:

Foo(Foo& other)
    : Foo(const_cast<const Foo&>(other)) {}

Когда этот конструктор определен, все работает снова: теперь предпочтительна копия аргумента non-const Foo. Однако это кажется очень отрывочным для меня, поскольку это "лечение" кажется хуже, чем болезнь.

Есть ли другой способ достижения этого эффекта, чтобы указать, что конструктор естественных копий должен быть предпочтительнее конструктора переменных? Если нет, есть ли какие-либо неблагоприятные последствия для определения этого конструктора копии неконстантного аргумента?

4b9b3361

Ответ 1

Вы можете использовать некоторые уродливые SFINAE с std::enable_if, но я не уверен, что это лучше, чем ваше первоначальное решение (на самом деле, я уверен, что это хуже!):

#include <memory>
#include <type_traits>

// helper that was not included in C++11
template<bool B, typename T = void> using disable_if = std::enable_if<!B, T>;

template<typename T>
struct Foo {

    Foo() = default;
    Foo(const Foo &) = default;

    template<typename Arg, typename ...Args, typename = typename
        disable_if<
            sizeof...(Args) == 0 &&
            std::is_same<typename
                std::remove_reference<Arg>::type,
                Foo
            >::value
        >::type
    >
    Foo(Arg&& arg, Args&&... args)
        : t(std::forward<Arg>(arg), std::forward<Args>(args)...) {}

    T t;
};

int main(int argc, char* argv[]) {
    Foo<std::shared_ptr<int>> x(new int(42));
    decltype(x) copy_of_x(x);
    decltype(x) copy_of_temp(Foo<std::shared_ptr<int>>(new int));
    return 0;
}

Ответ 2

Лучший подход - не делать то, что вы делаете.

Тем не менее, простое исправление заключается в том, чтобы позволить конструктору Variadic перейти к конструктору базового класса с некоторым специальным первым аргументом.

например. следующие компиляции с MinGW g++ 4.7.1:

#include <iostream>         // std::wcout, std::endl
#include <memory>           // std::shared_ptr
#include <stdlib.h>         // EXIT_SUCCESS
#include <tuple>
#include <utility>          // std::forward

void say( char const* const s ) { std::wcout << s << std::endl; }

template<typename T>
struct Foo;

namespace detail {
    template<typename T>
    struct Foo_Base
    {
        enum Variadic { variadic };

        Foo_Base()
            : t()
        { say( "default-init" ); }

        Foo_Base( Foo_Base const& other )
            : t( other.t )
        { say( "copy-init" ); }

        template<typename ...Args>
        Foo_Base( Variadic, Args&&... args )
            : t( std::forward<Args>(args)... )
        { say( "variadic-init" ); }

        T t;
    };

    template<typename T>
    struct Foo_ConstructorDispatch
        : public Foo_Base<T>
    {
        Foo_ConstructorDispatch()
            : Foo_Base<T>()
        {}

        template<typename ...Args>
        Foo_ConstructorDispatch( std::tuple<Foo<T>&>*, Args&&... args )
            : Foo_Base<T>( args... )
        {}

        template<typename ...Args>
        Foo_ConstructorDispatch( std::tuple<Foo<T> const&>*, Args&&... args )
            : Foo_Base<T>( args... )
        {}

        template<typename ...Args>
        Foo_ConstructorDispatch( void*, Args&&... args)
            : Foo_Base<T>( Foo_Base<T>::variadic, std::forward<Args>(args)... )
        {}
    };
}  // namespace detail

template<typename T>
struct Foo
    : public detail::Foo_ConstructorDispatch<T>
{
    template<typename ...Args>
    Foo( Args&&... args)
        : detail::Foo_ConstructorDispatch<T>(
            (std::tuple<Args...>*)0,
            std::forward<Args>(args)...
            )
    {}
};

int main()
{
    Foo<std::shared_ptr<int>>   x( new int( 42 ) );
    decltype(x)                 copy_of_x( x );
}

Ответ 3

Если нет, есть ли какие-либо неблагоприятные последствия для определения этого конструктора копии неконстантного аргумента?

Я проигнорирую "Если нет", поскольку существуют другие подходы. Но есть неблагоприятные последствия вашего подхода. Следующее все еще использует конструктор шаблона

Foo<X> g();
Foo<X> f(g());

Поскольку g() является rvalue, шаблон лучше соответствует, потому что он выводит параметр в ссылку rvalue.

Ответ 4

Отключите конструктор, если тип аргумента является тем же типом, что и производным от него:

template<typename ThisType, typename ... Args>
struct is_this_or_derived : public std::false_type {};

template<typename ThisType, typename T>
struct is_this_or_derived<ThisType, T>
    : public std::is_base_of<std::decay_t<ThisType>, std::decay_t<T> >::type {};

template<typename ThisType, typename ... Args>
using disable_for_this_and_derived 
      = std::enable_if_t<!is_this_or_derived<ThisType, Args ...>::value>;

Используйте его как

template<typename ...Args
        , typename = disable_for_this_and_derived<Foo, Args ...> >
                                                //^^^^
                                                //this needs to be adjusted for each class
Foo(Args&&... args) : t(std::forward<Args>(args)...) {}