Incomplete type 'std::tuple_size' used in nested name specifier
#include <iostream>
#include <tuple>
#include <utility>
template <typename T, std::size_t... I>
std::ostream& printTupleImp(std::ostream& os, const T& tp, std::index_sequence<I...>) {
std::size_t index = 0;
const auto printElem = [&index, &os](const auto& x) {
if (index++ > 0)
os << ", ";
os << x;
};
os << "(";
(printElem(std::get<I>(tp)), ...);
os << ")";
return os;
}
template <typename T, std::size_t Size = std::tuple_size_v<T>>
std::ostream& operator <<(std::ostream& os, const T& tp) {
return printTupleImp(os, tp, std::make_index_sequence<Size>{});
}
int main() {
std::cout << std::tuple{1, 2., "3"} << '\n';
}
Почему программа для печати тапла не компилируется?
error: incomplete type 'std::tuple_size' used in nested name specifier
Ответы (2 шт):
std::tuple_size_v<T> - не SFINAE-friendly, т.е. попытка передать в него неправильный тип приводит сразу к ошибке компиляции, а не к такой ошибке, которую можно остановить SFINAE.
Судя по ошибке, компилятор пытается вызвать ваш operator<< на char (видимо \n?), с которым, конечно, tuple_size работать не умеет.
Решение - заменить std::tuple_size_v<T> на std::tuple_size<T>::value, который является SFINAE-friendly.
Тогда попытка сделать tuple_size<char> будет остановлена SFINAE, и будет вызван следующий подходящий operator<< для char - стандартный.
Но вообще, такая перегрузка оператора - плохая идея. Их нужно перегружать в том же неймспейсе, что и один из параметров - чтобы ADL работал.
Например, следующий код не соберется с вашим оператором (даже с исправленным):
namespace NS
{
struct A {};
void operator<<(A, A) {}
void foo()
{
std::cout << std::tuple{1, 2., "3"} << '\n';
}
}
Тут бы помогло задвинуть ваш << в namespace std, но это запрещено.
Не компилируется т.к. ваша реализация оператора << случайно вызывается также для '\n' символа, который конечно не будет работать т.к. не является тьюплом.
Это легко исправляется, если указать вторым аргументом конкретно std::tuple, как я сделал ниже.
В моей версии можно даже упростить и вместо tuple_size_v использовать просто std::size_t Size = sizeof...(Args). Либо вообще убрать Size и просто в коде сделать std::index_sequence_for<Args...>{} вместо std::make_index_sequence<Size>{}.
#include <iostream>
#include <tuple>
#include <utility>
template <typename T, std::size_t... I>
std::ostream& printTupleImp(std::ostream& os, const T& tp, std::index_sequence<I...>) {
std::size_t index = 0;
const auto printElem = [&index, &os](const auto& x) {
if (index++ > 0)
os << ", ";
os << x;
};
os << "(";
(printElem(std::get<I>(tp)), ...);
os << ")";
return os;
}
template <typename ... Args, std::size_t Size = std::tuple_size_v<std::tuple<Args...>>>
std::ostream& operator << (std::ostream& os, const std::tuple<Args...> & tp) {
return printTupleImp(os, tp, std::make_index_sequence<Size>{});
}
int main() {
std::cout << std::tuple{1, 2., "3"} << '\n';
}
Вывод:
(1, 2, 3)
Решил учесть замечание от @HolyBlackCat, и реализовать поддержку не только std::tuple, но и std::array и std::pair. Сделал это через оператор requires (а не SFINAE):
#include <iomanip>
#include <iostream>
#include <tuple>
#include <utility>
#include <array>
template <typename T, std::size_t... I>
std::ostream& printTupleImp(std::ostream& os, const T& tp, std::index_sequence<I...>) {
std::size_t index = 0;
const auto printElem = [&index, &os](const auto& x) {
if (index++ > 0)
os << ", ";
os << x;
};
os << std::boolalpha << "(";
(printElem(std::get<I>(tp)), ...);
os << ")";
return os;
}
template <typename T> struct IsTuple : std::false_type {};
template <typename ... Args> struct IsTuple<std::tuple<Args...>> : std::true_type {};
template <typename T> struct IsArray : std::false_type {};
template <typename T, std::size_t Size> struct IsArray<std::array<T, Size>> : std::true_type {};
template <typename T> struct IsPair : std::false_type {};
template <typename A, typename B> struct IsPair<std::pair<A, B>> : std::true_type {};
template <typename T>
requires IsTuple<T>::value || IsArray<T>::value || IsPair<T>::value
std::ostream& operator << (std::ostream& os, const T & tp) {
std::size_t constexpr Size = std::tuple_size_v<std::decay_t<T>>;
return printTupleImp(os, tp, std::make_index_sequence<Size>{});
}
int main() {
std::cout
<< std::tuple{true, 2., "str"} << '\n'
<< std::array{4, 5, 6} << '\n'
<< std::pair{false, 'c'} << '\n'
;
}
Вывод:
(true, 2, str)
(4, 5, 6)
(false, c)
Напомню что вместо:
template <typename T>
requires IsTuple<T>::value || IsArray<T>::value || IsPair<T>::value
можно использовать:
template <typename T, typename Enable = std::enable_if_t<
IsTuple<T>::value || IsArray<T>::value || IsPair<T>::value>>