33

Tを所有するテンプレート「Foo」があり、引数を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;
}

なぜなら、この回答によれば、引数が一定でないため、可変個引数コンストラクターがより適切に一致するからです。明らかな理由で、発信者にconst_castの使用を強制したくありません。

私が見つけた1つの可能な解決策は、非const Fooを受け取り、コンストラクター転送を使用するFooの「コピーコンストラクター」を作成することでした。

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

このコンストラクターが定義されると、物事は再び機能します。現在、非constFoo引数copyctorが優先されます。しかし、これは私には非常に大雑把に思えます。この「治癒」は病気よりも悪いように思われるからです。

この効果を実現する別の方法はありますか?可変個引数コンストラクターよりも自然コピーコンストラクターを優先する必要があることを示しますか?そうでない場合、この非定数引数コピーコンストラクターを定義することの悪影響はありますか?

4

4 に答える 4

15

でいくつかの醜い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;
}
于 2012-12-18T17:25:16.933 に答える
2

最善のアプローチは、あなたがしていることをしないことです。

とはいえ、簡単な修正は、可変引数コンストラクターを基本クラスのコンストラクターに転送できるようにすることです。特別な最初の引数を使用します。

たとえば、次は 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 );
}
于 2012-12-18T18:22:20.767 に答える
2

そうでない場合、この const 以外の引数のコピー コンストラクターを定義することによる悪影響はありますか?

他のアプローチがあるため、「そうでない場合」は無視します。しかし、あなたのアプローチに悪影響があります。以下はまだテンプレートコンストラクターを使用しています

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

は右辺値であるためg()、パラメーターを右辺値参照に推定するため、テンプレートの方が適切に一致します。

于 2012-12-18T22:36:21.187 に答える
1

引数の型が this と同じ型または派生した型である場合、コンストラクターを無効にします。

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)...) {}
于 2016-02-11T22:15:11.707 に答える