Невозможно использовать std::apply для пользовательских типов
При реализации
compressed_tuple
class для какого-то проекта, над которым я работаю, я столкнулся со следующей проблемой: я не могу передать экземпляры этого типа в std ::apply, хотя это должно быть возможно в соответствии с: https://en.cppreference.com / w / cpp / utility / apply.
Мне удалось довольно легко воспроизвести проблему, используя следующий фрагмент (Godbolt ):
#include <tuple>
struct Foo {
public:
explicit Foo(int a) : a{ a } {}
auto &get_a() const { return a; }
auto &get_a() { return a; }
private:
int a;
};
namespace std {
template<>
struct tuple_size<Foo> {
constexpr static auto value = 1;
};
template<>
struct tuple_element<0, Foo> {
using type = int;
};
template<size_t I>
constexpr auto get(Foo &t) -> int & {
return t.get_a();
}
template<size_t I>
constexpr auto get(const Foo &t) -> const int & {
return t.get_a();
}
template<size_t I>
constexpr auto get(Foo &&t) -> int && {
return std::move(t.get_a());
}
template<size_t I>
constexpr auto get(const Foo &&t) -> const int && {
return move(t.get_a());
}
} // namespace std
auto foo = Foo{ 1 };
auto f = [](int) { return 2; };
auto result = std::apply(f, foo);
Когда я пытаюсь скомпилировать этот фрагмент кода, мне кажется, что он не может найти
std::get
перегрузки, которые я определил, даже если они должны идеально совпадать. Вместо этого он пытается сопоставить все другие перегрузки (std ::get(pair<T, U>), std::get(array<...>) и т. Д.), Не говоря уже о моих перегрузках. Я получаю постоянные ошибки во всех трех основных компиляторах (MSVC, Clang, GCC).
Итак, мой вопрос в том, является ли это ожидаемым поведением и просто невозможно использовать
std::apply
с пользовательскими типами? И есть ли обходной путь?
2 ответа
Итак, мой вопрос в том, является ли это ожидаемым поведением, и просто невозможно использовать std ::apply с пользовательскими типами?
Нет, нет.
В [tuple.apply] используется
std:get
внутренне. Поскольку пользователям запрещено определять под
namespace std
, в настоящее время невозможно применить к пользовательским типам.
Также в реализациях libstdc++, libc ++ и MSVC-STL,
std::get
используется для внутреннего использования вместо неквалифицированного.
Вы можете спросить, в [tuple.creation] стандарт описывает
tuple_cat
следующее:
[Примечание 1. Реализация может поддерживать дополнительные типы в пакете параметров шаблона.
Tuples
которые поддерживают протокол -подобный, напримерpair
а такжеarray
. - конец примечания]
Означает ли это, что другие служебные функции, например, должны поддерживать -подобные типы?
Обратите внимание, что, в частности, термин "
tuple
-like "не имеет конкретного определения на данный момент. Это было намеренно оставлено комитетом C++, чтобы заполнить этот пробел будущим предложением. Существует предложение, которое собирается начать улучшать этот вопрос, см. P2165R2 .
И есть ли обходной путь?
Текущий обходной путь - заново реализовать собственный
std::apply
и использовать неквалифицированный
get
внутренне.
На вопрос полностью ответил @康桓瑋. Я собираюсь опубликовать более подробную информацию о том, как обеспечить обходной путь.
Во-первых, вот общий C++20
apply
функция:
#include<tuple>
#include<functional>
namespace my
{
constexpr decltype(auto) apply(auto&& function, auto&& tuple)
{
return []<size_t ... I>(auto && function, auto && tuple, std::index_sequence<I...>)
{
using std::get;
return std::invoke(std::forward<decltype(function)>(function)
, get<I>(std::forward<decltype(tuple)>(tuple)) ...);
}(std::forward<decltype(function)>(function)
, std::forward<decltype(tuple)>(tuple)
, std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<decltype(tuple)> > >{});
}
} //namespace my
Собственное пространство имен полезно для того, чтобы пользовательское применение не мешало стандартной версии. Безоговорочный призыв к
get
означает (цитируя @Quuxplusone из его блога, который дает лучшее объяснение, с которым я когда-либо сталкивался):
Неквалифицированный вызов с использованием двухэтапного вызова, например my::xyzzy;xyzzy(t) указывает: «Я знаю один способ xyzzy, что бы это ни было, но сам T может знать способ получше. Если у Т есть мнение, ты должен доверять Т, а не мне».
Затем вы можете развернуть свой собственный класс, похожий на кортеж,
struct my_tuple
{
std::tuple<int,int> t;
};
template<size_t I>
auto get(my_tuple t)
{
return std::get<I>(t.t);
}
namespace std
{
template<>
struct tuple_size<my_tuple>
{
static constexpr size_t value = 2;
};
}
При перегрузке
get()
и специализация
std::tuple_size
, функция применения затем работает должным образом. Более того, вы можете подключить любой совместимый стандартный тип:
int main()
{
auto test = [](auto ... x) { return 1; };
my::apply(test, my_tuple{});
my::apply(test, std::tuple<int,double>{});
my::apply(test, std::pair<int,double>{});
my::apply(test, std::array<std::string,10>{});
}