この回答 は、スタンドアロンstd::variant
をストリーミングする方法を説明しています。ただし、std::variant
がstd::unordered_map
に格納されている場合は機能しないようです。
次の 例 :
#include <iostream>
#include <string>
#include <variant>
#include <complex>
#include <unordered_map>
// https://stackoverflow.com/a/46893057/8414561
template<typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<Ts...>& v)
{
std::visit([&os](auto&& arg) {
os << arg;
}, v);
return os;
}
int main()
{
using namespace std::complex_literals;
std::unordered_map<int, std::variant<int, std::string, double, std::complex<double>>> map{
{0, 4},
{1, "hello"},
{2, 3.14},
{3, 2. + 3i}
};
for (const auto& [key, value] : map)
std::cout << key << "=" << value << std::endl;
}
でコンパイルできません:
In file included from main.cpp:3:
/usr/local/include/c++/8.1.0/variant: In instantiation of 'constexpr const bool std::__detail::__variant::_Traits<>::_S_default_ctor':
/usr/local/include/c++/8.1.0/variant:1038:11: required from 'class std::variant<>'
main.cpp:27:50: required from here
/usr/local/include/c++/8.1.0/variant:300:4: error: invalid use of incomplete type 'struct std::__detail::__variant::_Nth_type<0>'
is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/include/c++/8.1.0/variant:58:12: note: declaration of 'struct std::__detail::__variant::_Nth_type<0>'
struct _Nth_type;
^~~~~~~~~
/usr/local/include/c++/8.1.0/variant: In instantiation of 'class std::variant<>':
main.cpp:27:50: required from here
/usr/local/include/c++/8.1.0/variant:1051:39: error: static assertion failed: variant must have at least one alternative
static_assert(sizeof...(_Types) > 0,
~~~~~~~~~~~~~~~~~~^~~
なぜそれが起こるのですか?どうすれば修正できますか?
[temp.arg.explicit]/ には、次のすばらしい文があります。
他の方法では推定されない後続のテンプレートパラメータパックは、空のテンプレート引数のシーケンスに推定されます。
これは何を意味するのでしょうか?末尾のテンプレートパラメータパックとは何ですか?さもなければ演繹されないことの意味は何ですかこれらはすべて、本当に答えがない良い質問です。しかし、これは非常に興味深い結果をもたらします。考慮してください:
template <typename... Ts> void f(std::Tuple<Ts...>);
f({}); // ok??
これは...整形式です。 Ts...
を推定できないため、空であると推定します。これにより、完全に有効な型であるstd::Tuple<>
、および{}
でインスタンス化することさえできる完全に有効な型が残ります。これでコンパイルできます!
では、空のパラメータパックから推論したものが有効な型ではないとどうなりますか?これが 例 です。
template <class... Ts>
struct Y
{
static_assert(sizeof...(Ts)>0, "!");
};
template <class... Ts>
std::ostream& operator<<(std::ostream& os, Y<Ts...> const& )
{
return os << std::endl;
}
operator<<
は潜在的な候補ですが、控除は失敗します...と思われます。 Ts...
が空であると思いつくまで。ただし、Y<>
は無効なタイプです! Y<>
からstd::endl
を構築できないことを確認することすらしません-すでに失敗しています 。
variant<>
は有効な型ではないため、これは基本的にvariant
の場合と同じです。
簡単な修正は、関数テンプレートを単にvariant<Ts...>
からvariant<T, Ts...>
に変更することです。これはvariant<>
に推定できなくなりました。これは可能ではないので、問題はありません。
何らかの理由で、あなたのコード(私には正しいように見えます)がstd::variant<>
(空の代替)をclangとgccの両方でインスタンス化しようとしています。
私が見つけた回避策は、特に空ではないバリアントのテンプレートを作成することです。とにかくstd::variant
を空にすることはできないので、空ではないバリアント用の汎用関数を書くのが通常は良いと思います。
template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)
{
std::visit([&os](auto&& arg) {
os << arg;
}, v);
return os;
}
この変更により、コードが機能します。
また、std::variant
がstd::variant<>
の特殊化なしの単一引数コンストラクターを持っている場合、この問題は発生しないこともわかりましたそもそも起こった。 https://godbolt.org/z/VGih_4 の最初の行と、それがどのように機能するかを確認してください。
namespace std{
template<> struct variant<>{ ... no single-argument constructor, optionally add static assert code ... };
}
ポイントを説明するためだけにこれを行っていますが、これを行うことを必ずしもお勧めしません。
問題はstd::endl
ですが、 std :: basic_ostream :: operator << のオーバーロードよりもオーバーロードが優れている理由に困惑しています。/を参照してください godbolt live example :
<source>:29:12: note: in instantiation of template class 'std::variant<>' requested here
<< std::endl;
^
std::endl
を削除すると、実際に問題が修正されます。 liveon Wandbox を参照してください。
空のバリアントを許可しないようにオペレーターを変更することをalfCが指摘しているように、実際に問題は修正されます、 実際に見る :
template<typename T, typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::variant<T, Ts...>& v)