誰かが私に、少なくとも私の知る限り、C++が強く型付けされたEllipsis関数を実装しない理由を説明してもらえますか?
void foo(double ...) {
// Do Something
}
つまり、ユーザーはfoo関数に可変数の用語を渡すことができますが、すべての用語はdoubleでなければなりません。
歴史的に、Ellipsis構文_...
_はCから来ています。
この複雑な獣は、printf
のような関数を強化するために使用され、_va_list
_、_va_start
_などで使用されます...
お気づきのように、これはタイプセーフではありません。しかし、Cはタイプセーフではありません。ポインタ型の_void*
_との間の暗黙的な変換、整数/浮動小数点値の暗黙的な切り捨てなどにより...
C++はCのスーパーセットとして可能な限り近くなるため、Cから省略記号を継承しました。
創業以来、C++のプラクティスは進化し、より強力なタイピングへの強いプッシュがありました。
C++ 11では、これは最高潮に達しました:
foo({1, 2, 3, 4, 5})
printf
を記述できる可変型テンプレートVariadicテンプレートは実際に、構文でEllipsis _...
_を再利用して、型または値のpacksを示し、アンパック演算子として使用します。
_void print(std::ostream&) {}
template <typename T, typename... Args>
void print(std::ostream& out, T const& t, Args const&... args) {
print(out << t, args...); // recursive, unless there are no args left
// (in that case, it calls the first overload
// instead of recursing.)
}
_
_...
_の3つの異なる使用法に注意してください。
typename...
_可変個の型を宣言するArgs const&...
_は、引数のパックを宣言しますargs...
_パックを式に解凍する有る
void foo(std::initializer_list<double> values);
// foo( {1.5, 3.14, 2.7} );
それに非常に近いです。
可変テンプレートを使用することもできますが、それはより議論的なものになります。実際の理由については、その新しい構文を取り込む努力はおそらくそれだけの価値はないと思います。単一の要素にどのようにアクセスするのですか?いつ停止するかをどうやって知っていますか?何より、たとえばstd::initializer_list
?
C++には、これにさらに近いものがあります。非型パラメーターパックです。
template < non-type ... values>
のように
template <int ... Ints>
void foo()
{
for (int i : {Ints...} )
// do something with i
}
ただし、非タイプテンプレートパラメータ(uhm)のタイプにはいくつかの制限があります。たとえば、double
にすることはできません。
可変テンプレートとSFINAEですでに可能です:
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template <class... Doubles, class = std::enable_if_t<
all_true<std::is_convertible<Doubles, double>{}...>{}
>>
void foo(Doubles... args) {}
ありがとう コロンボ ニースのためにall_true
騙す。また、C++ 17でフォールド式を使用することもできます。
将来の標準では、terser構文(簡潔なforループ、暗黙の関数テンプレート...)に焦点が当てられているため、提案された構文が標準で1日になる可能性が非常に高くなります;)
具体的にそのようなことが提案されなかった(または提案されて拒否された)理由については、わかりません。このようなことは確かに便利ですが、言語をさらに複雑にします。 Quentin が示すように、テンプレートでそのようなことを実現するC++ 11の方法はすでに提案されています。
コンセプトが標準に追加されると、別のより簡潔な方法が得られます。
template <Convertible<double>... Args>
void foo(Args... doubles);
または
template <typename... Args>
requires Convertible<Args, double>()...
void foo(Args... doubles);
または @ dypが指摘するように :
void foo(Convertible<double>... doubles);
個人的には、現在の解決策とConceptsで得られる解決策の間で、それが問題に対する適切な解決策だと感じています。特に最後のものは基本的にあなたがもともと求めていたものだからです。
template<typename T, typename... Arguments>
struct are_same;
template <typename T, typename A1, typename... Args>
struct are_same<T, A1, Args...>{ static const bool value = std::is_same<T, A1>::value && are_same<T, Args...>::value;};
template <typename T>
struct are_same<T>{static const bool value = true;};
template<typename T, typename... Arguments>
using requires_same = std::enable_if_t<are_same<T, Arguments...>::value>;
template <typename... Arguments, typename = requires_same<double, Arguments...>>
void foo(Arguments ... parameters)
{
}
マシューの答え に基づく:
void foo () {}
template <typename... Rest>
void foo (double arg, Rest... rest)
{
/* do something with arg */
foo(rest...);
}
foo
を使用するコードがコンパイルされると、すべての引数がdouble
に変換可能であることがわかります。
あなたが提案することの(一種の)達成方法は、可変テンプレートを使用することです
template<typename... Arguments>
void foo(Arguments... parameters);
ただし、パラメーターパックで任意の型を渡すことができます。あなたが提案したことは実装されたことはありません。多分それは言語への大きな追加かもしれませんし、現状では実装するのが難しすぎるかもしれません。あなたはいつも提案を書いて、それをisocpp.orgに提出することを試みることができます
使えるから
void foo(std::vector<T> values);