14

あらすじ

引数を実装クラスに転送する可変個引数テンプレート コンストラクターを持つ型が与えられた場合、SFINAE で転送される型を制限することは可能ですか?

詳細

最初に、コンストラクターがユニバーサル参照を取る非可変個のケースを考えてみましょう。ここでは、SFINAE を介して非 const 左辺値参照の転送を無効にして、代わりにコピー コンストラクターを使用できます。

struct foo
{
  foo() = default;

  foo(foo const&) 
  {
      std::cout << "copy" << std::endl;
  }

  template <
    typename T,
    typename Dummy = typename std::enable_if<
      !std::is_same<
          T,
          typename std::add_lvalue_reference<foo>::type
      >::value
    >::type
  >
  foo(T&& x)
    : impl(std::forward<T>(x))
  {
      std::cout << "uref" << std::endl;
  }

  foo_impl impl;
};

fooユニバーサル参照のこの制限は便利です。そうしないと、実装クラスが、認識していないtype の非 const 左辺値参照を受け取るためです。完全な例は LWS にあります

質問

しかし、これは可変個引数テンプレートではどのように機能するのでしょうか? それはまったく可能ですか?もしそうなら、どのように?単純な拡張機能は機能しません:

template <
  typename... Args,
  typename Dummy = typename std::enable_if<
    !std::is_same<
        Args...,
        typename std::add_lvalue_reference<foo>::type
    >::value
  >::type
>
foo(Args&&... args)
  : impl(std::forward<Args>(args)...)
{
    std::cout << "uref" << std::endl;
}

LWSでも。)

編集: R. Martinho Fernandez が 2012 年にこの問題のバリエーションについてブログを書いていることがわかりました: http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html

4

2 に答える 2

19

以下は、適切に制約されたコンストラクター テンプレートを作成するさまざまな方法です。複雑さの昇順、対応する機能の豊富さの昇順、落とし穴の数の降順で示します。

この特定の形式の EnableIfが使用されますが、これは実装の詳細であり、ここで概説されている手法の本質を変更しません。また、異なるメタ計算を組み合わせるためのAndおよびエイリアスがあることも想定されています。Not例えばAnd<std::is_integral<T>, Not<is_const<T>>>​​よりも便利ですstd::integral_constant<bool, std::is_integral<T>::value && !is_const<T>::value>

コンストラクター テンプレートに関しては、制約がまったくないよりも、制約がある方がはるかに優れているため、特定の戦略はお勧めしません。可能であれば、非常に明白な欠点を持つ最初の 2 つの手法は避けてください。残りの手法は、同じテーマに関する詳細な説明です。

自分自身を拘束する

template<typename T>
using Unqualified = typename std::remove_cv<
    typename std::remove_reference<T>::type
>::type;

struct foo {
    template<
        typename... Args
        , EnableIf<
            Not<std::is_same<foo, Unqualified<Args>>...>
        >...
    >
    foo(Args&&... args);
};

利点:次のシナリオで、コンストラクターがオーバーロードの解決に参加するのを回避します。

foo f;
foo g = f; // typical copy constructor taking foo const& is not preferred!

欠点:他のすべての種類の過負荷解決に参加します

構築式の制約

foo_implコンストラクターにはfromを構築するという道徳的効果があるためArgs、これらの正確な用語に対する制約を表現するのは自然なことのように思われます。

    template<
        typename... Args
        , EnableIf<
            std::is_constructible<foo_impl, Args...>
        >...
    >
    foo(Args&&... args);

利点:これは、何らかのセマンティック条件が満たされた場合にのみオーバーロードの解決に参加するため、正式に制約付きテンプレートになりました。

欠点:以下は有効ですか?

// function declaration
void fun(foo f);
fun(42);

たとえば、foo_implがの場合、std::vector<double>はい、コードは有効です。はそのような型のベクトルを作成std::vector<double> v(42);する有効な方法であるため、からへの変換は有効です。言い換えれば、他のコンストラクタの問題を脇に置いてください(パラメータの順序が入れ替わっていることに注意してください-それは残念です)。intfoostd::is_convertible<T, foo>::value == std::is_constructible<foo_impl, T>::valuefoo

構築式を明示的に制約する

当然、次のことがすぐに思い浮かびます。

    template<
        typename... Args
        , EnableIf<
            std::is_constructible<foo_impl, Args...>
        >...
    >
    explicit foo(Args&&... args);

コンストラクターをマークする 2 回目の試行explicit

メリット:上記のデメリットを回避!また、それを忘れない限り、それほど時間はかかりませんexplicit

欠点:foo_implがの場合std::string、次のことが不便な場合があります。

void fun(foo f);
// No:
// fun("hello");
fun(foo { "hello" });

fooたとえば、 が の薄いラッパーであることを意図しているかどうかによって異なりますfoo_impl。これが、より厄介な欠点であると私が考えるものfoo_implですstd::pair<int, double*>

foo make_foo()
{
    // No:
    // return { 42, nullptr };
    return foo { 42, nullptr };
}

ここで実際に何かから私を救うような気がしませんexplicit.中括弧には2つの引数があるため、明らかに変換ではなく、型はfooすでに署名に表示されているため、そうであると感じたときにそれを惜しみません.冗長。std::tupleその問題に苦しんでいます(工場std::make_tupleはその痛みを少し和らげますが)。

建設からの変換を個別に制約する

構築変換の制約を別々に表現してみましょう:

// New trait that describes e.g.
// []() -> T { return { std::declval<Args>()... }; }
template<typename T, typename... Args>
struct is_perfectly_convertible_from: std::is_constructible<T, Args...> {};

template<typename T, typename U>
struct is_perfectly_convertible_from: std::is_convertible<U, T> {};

// New constructible trait that will take care that as a constraint it
// doesn't overlap with the trait above for the purposes of SFINAE
template<typename T, typename U>
struct is_perfectly_constructible
: And<
    std::is_constructible<T, U>
    , Not<std::is_convertible<U, T>>
> {};

使用法:

struct foo {
    // General constructor
    template<
        typename... Args
        , EnableIf< is_perfectly_convertible_from<foo_impl, Args...> >...
    >
    foo(Args&&... args);

    // Special unary, non-convertible case
    template<
        typename Arg
        , EnableIf< is_perfectly_constructible<foo_impl, Arg> >...
    >
    explicit foo(Arg&& arg);
};

利点:の構築と変換は、 の構築と変換のfoo_impl必要十分条件になりましfooた。つまりstd::is_convertible<T, foo>::value == std::is_convertible<T, foo_impl>::valuestd::is_constructible<foo, Ts...>::value == std::is_constructible<foo_impl, T>::valueどちらも (ほぼ) 成立します。

欠点? たとえばのfoo f { 0, 1, 2, 3, 4 };場合は機能しません。これは、制約がスタイルの構築に関するものであるためです。制約のある(読者への演習として残されている)オーバーロード取得をさらに追加することも、さらにはオーバーロード取得(制約も読者への演習として残されている) を追加することも可能ですが、複数からの「変換」であることを覚えておいてください。引数は構造ではありません!)。重複を避けるために変更する必要はないことに注意してください。foo_implstd::vector<int>std::vector<int> v(0, 1, 2, 3, 4);std::initializer_list<T>std::is_convertible<std::initializer_list<T>, foo_impl>std::initializer_list<T>, Ts&&...is_perfectly_convertible_from

私たちの間でより卑劣な人は、狭いコンバージョンを他の種類のコンバージョンと区別するようにします.

于 2012-11-11T04:12:16.870 に答える
4

Argsより複雑な式を入れて、 のように展開できexpression(Args)...ます。したがって

!std::is_same<Args, typename std::add_lvalue_reference<foo>::type>::value...

is_same各引数のコンマ区切りのリストが表示されます。それに応じて値を組み合わせたテンプレートへのテンプレート引数としてそれを使用すると、次のようなものが得られます。

template<bool... Args> struct and_;

template<bool A, bool... Args>
struct and_<A, Args...>{
  static constexpr bool value = A && and_<Args...>::value;
};
template<bool A>
struct and_<A>{
  static constexpr bool value = A;
};

//...
template <typename... Args,
          typename Dummy = typename std::enable_if<
              and_<!std::is_same<Args, 
                                 typename std::add_lvalue_reference<foo>::type
                                >::value...>::value
              >::type
         >
foo(Args&&... args) : impl(std::forward<Args>(args)...)
{
  std::cout << "uref" << std::endl;
}

引数をどの程度正確に制限したいかは完全にはわかりません。したがって、これで目的が達成できるかどうかはわかりませんが、原則を使用できるはずです。

于 2012-11-08T20:22:23.000 に答える