web-dev-qa-db-ja.com

配列、ベクトル、構造体などを使用せずに可変関数または可変テンプレート関数に渡されるすべての引数に1つの型を指定しますか?

不明な数の引数を受け入れる必要のある関数(おそらくメンバー関数であり、重要ではない...多分それはそうでしょうか?)を作成していますが、それらすべてを同じ型にしたいと思っています。配列またはベクトルを渡すことができることはわかっていますが、追加の構造体や追加のブラケットなしで直接argsのリストを受け入れることができるようにしたいと考えています。それ自体は可変長関数がタイプセーフであるようには見えません、そして可変長テンプレート関数を使ってこれをどのように行うかわかりませんでした。ここに私が本質的に目指しているものがあります(おそらく正しいコードではなく、完全にドラゴンのリストを取得する目的ではありません):

//typedef for dragon_list_t up here somewhere.

enum Maiden {
    Eunice
    , Beatrice
    , Una_Brow
    , Helga
    , Aida
};

dragon_list_t make_dragon_list(Maiden...) {
    //here be dragons
}

OR

template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
    //here be dragons
}

使用法

dragon_list_t dragons_to_slay
    = make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;

上記と同様のことをいくつか試しましたが、サイコロはありません。提案?私がしたかもしれない明らかな見落とし?代わりにこれを行うのは大したことではないかもしれません。

dragon_list_t make_dragon_list(std::array<Maiden> maidens) {
    //here be dragons.
}
dragon_list_t dragons_to_slay
    = make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida})
;

しかし、可能であれば、私はむしろ最初の方法でそれを実行できるようにしたいです。

40
Brett Rossier

Variadicテンプレートによって引数を受け入れるだけで、後で変換時に型チェックで有効性をチェックできます。

ただし、SFINAEを使用するなどして、完全に間違った引数を拒否するためのオーバーロード解決を利用するために、関数インターフェイスレベルで変換可能性を確認できます。

template<typename R, typename...> struct fst { typedef R type; };

template<typename ...Args>
typename fst<void, 
  typename enable_if<
    is_convertible<Args, ToType>::value
  >::type...
>::type 
f(Args...);

ユースケースとして、std::array<>からdragon_list_tに移動する手順がわかっている場合は、上記の最初のオプション( "convert-later")に従って解決済みです。

template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
    std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
    // here be dragons
}

これを上記のis_convertibleアプローチと組み合わせると、引数のオーバーロード解決も行い、該当しない場合は拒否するリジェクトアーリーテンプレートが作成されます。

パックにないパラメーターでtemplateを使用しない場合、可変個関数は解決され、すべての引数が同じ型になります。

以下は、maxs(またはintに変換可能な型)のみを受け入れる拡張int関数の例です。

_int maximum(int n) // last argument must be an `int`
{
    return n;
}

template<typename... Args>
int maximum(int n, Args... args) // first argument must be an int
{
    return std::max(n, maximum(args...));
}
_

説明:引数パック(_args..._)を解凍すると、コンパイラは最適なオーバーロードを探します。パックにパラメーターが1つしかない場合、最適な候補はmaximum(int)であるため、パラメーターはint(またはintに変換可能)でなければなりません。パックに複数の要素がある場合、候補はmaximum(int, typename...)だけなので、最初の引数はint(またはintに変換可能)でなければなりません。パックのすべての型がintに変換可能な型でなければならないことを帰納法で証明するのは簡単です。

17
Motti

C++ 0xタグを含めたので、明白な答えは initializer list を検索することです。イニシャライザリストを使用すると、Actorが処理する単一のデータ構造に自動的に変換される、Actorへの多数の引数を指定できます。

それらの主な(排他的?)使用は、まさにあなたが言及したような種類の状況で、ある種のリスト/配列/オブジェクトのその他のコレクションの作成に使用する同じタイプの引数をいくつか渡します。 (一例として)std::vectorによってサポートされるため、次のようなものを使用できます。

std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};

3つのdragonオブジェクトのベクトルを作成します。他のコレクションのほとんど(すべて?)にも同じものが含まれるので、本当にドラゴンのリストを要求すれば、それもかなり簡単に取得できるはずです。

11
Jerry Coffin

最近、パラメーターパックを1つのタイプのみ、または少なくともそのタイプに変換できるように制約する必要がありました。私は別の方法を見つけました:

#include <type_traits>
#include <string>

template <template<typename> class Trait, typename Head, typename ...Tail> 
struct check_all {
  enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value };
};

template <template<typename> class Trait, typename Head>
struct check_all<Trait, Head> {
  enum { value = Trait<Head>::value };
};

template <typename ...Args> 
struct foo {
  // Using C++11 template alias as compile time std::bind
  template <typename T>
  using Requirement = std::is_convertible<double, T>;
  static_assert(check_all<Requirement, Args...>::value, "Must convert to double");
};

int main() {
  foo<int, char, float, double>();
  foo<int, std::string>(); // Errors, no conversion
}

このソリューションで気に入ったのは、check_all他の特性にも。

5
Flexo

最近の提案 Homogeneous variadic functions は、最初の構文のようなものを合法にすることでこれに対処しています。もちろん、パラメータパックを使用するには、名前を付ける必要があります。また、正確な構文はまだあまり具体的ではないようです。

したがって、この提案の下では、これは実際には合法です(このような構成要素は、論文の「テンプレート紹介」の段落で確認できます)。

dragon_list_t make_dragon_list(Maiden... maidens) {
    //here be dragons
}
5
Jan Hošek

質問のタグはC++ 11ですが、C++ 17 +の概念ソリューションは、GCCでのサポートがあり、他のメンバーもすぐにフォローするかのように、追加してみる価値があると思います。

最初に単純な概念を定義します

class mytype{};

template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;

次に、可変テンプレートテンプレートパラメータを使用します

template<MyType ... Args>
void func(Args &&... args){
    // do something here
}

コンセプトの出現により、はるかに簡単になりました!

2
CoffeeandCode

これは(Maiden ...)のほぼ直接的な代替手段です。

dragon_list_t make_dragon_list()
{
    return dragon_list_t();
}
template<typename ...T>
auto make_dragon_list(Maiden first, T &&... other) -> decltype(make_dragon_list(std::forward<T>(other)...))
{
    //here be dragons
    return {first, std::forward<T>(other)...};
}

しかし、それでも複数の変数による構築はサポートされていません。 Mainedがクラスの場合、2つの変数から構成できます。

make_dragon_list({1,0}) 

動作しますが、

make_dragon_list({1,0}, {2,3}) 

コンパイルされません。

このため、この提案は非常に重要です

2
jenkas

それは、実際に実装しようとしている内容によって異なります。

通常enumは、特定のクラスのランタイムサブタイプ、または識別された共用体(boost :: variant)を示します。ただし、この場合は、enumを直接渡します。さらに、可能な値のセットが制限されており、各関数呼び出しはサブセットを形成します。実際に表現しているのは1つのサブセットであり、いくつかのパラメーターではありません。

有限セットのサブセットを表す最良の方法はビットセットです。大きなセットはstd::bitsetを使用する必要があります。小さなセットはunsigned longを使用できます。

enum Maiden_set {
    Eunice = 1,
    , Beatrice = 2
    , Una_Brow = 4
    , Helga = 8
    , Aida = 16
};

dragon_list_t make_dragon_list(Maiden_set) {
    //here be dragons
}

make_dragon_list( Eunice + Beatrice + Helga );

または、コンパイル時にバリエーションを処理したいと思われるため、

template< int Maidens > // parameter is not a Maiden_set because enum+enum=int
dragon_list_t make_dragon_list() {
    //here be dragons
}

make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int

enumタイプでオーバーロードされたoperator+を使用して、2の累乗を自動的に生成できるはずです。しかし、私が完全に正しい軌道に乗っているとは思えません。

1
Potatoswatter

つまり、ベクターを作成するだけです。特に boost :: list_of やC++ 0xのイニシャライザリストを使用する場合は、それほどオーバーヘッドはありません。構文のオーバーヘッドは最小限に抑えられ、より柔軟になります(実行時にのみ既知の多数の引数でリストを渡すことができます)。

本当に必要な場合は、可変個のテンプレートパラメータを使用してこれを行うことができます。

// Using pass-by-value since I'm assuming it is primitive:

template< typename T, typename... Args>
void make_dragon_list_internal( dragon_list_t* dragon_list, T t, Args... args )
{
   // add T to dragon_list.
   make_dragon_list_internal( dragon_list, args... );
}

void make_dragon_list_internal( dragon_list_t* dragon_list )
{
   // Finalize dragon_list.
}

template<typename... Args>
dragon_list_t make_dragon_list( Args... args )
{
  dragon_list_t dragon_list;
  make_dragon_list_internal( &dragon_list, args... );
  return dragon_list;
}

これはタイプセーフで拡張可能です(必要に応じて、これをドラゴン以外のものにすることもできます)。

1
Todd Gardner

私は物事を単純に保つように努めます、そして私が考えることができる最も単純な解決策は、単純な古いベクトルを使用することです。 C++ 0x機能を使用することで、(厳密にではなくても)希望する構文に似た構文を取得できます。

void foo( std::vector<int> const & v ) {
   std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ") );
}
int main() {
  foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {}
}

次のコードはあなたのケースに役立つと思います:

template <class...>
struct IsAllSame {};

template <class T, class B1>
struct IsAllSame<T, B1> {
  static constexpr const bool kValue = std::is_same<T, B1>::value;
};

template <class T, class B1, class... Bn>
struct IsAllSame<T, B1, Bn...> {
  static constexpr const bool kValue =
      IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false;
};

IsAllSame<int>::kValue == true
IsAllSame<bool, int>::kValue == false
IsAllSame<bool, int, int>::kValue == false
0
Nan Hua