I'm trying to specialize std::tuple_size
and std::get
for a simple struct so that I can use std::apply
with it in a generic function. However, when I call std::apply
, I get a compilation error. Interestingly, if I copy the implementation of std::apply
and std::__apply_impl
into a local namespace and call that version instead, everything works fine.
Does anyone have any suggestions on what I might be doing wrong?
Here’s a minimal reproducible example.
#include <iostream>
#include <array>
#include <tuple>
#include <functional>
// Define a simple struct A with different types
struct A {
int i;
double d;
};
// Specialize std::tuple_size and std::get for A
namespace std {
template <> struct tuple_size<A> : std::integral_constant<std::size_t, 2> { }; // 3 members in A
template <std::size_t N> constexpr decltype(auto) get (const A& a) { if constexpr (N == 0) return (a.i); else if constexpr (N == 1) return (a.d); }
}
// I copied below the code for std::__apply_impl, and std::apply
namespace My {
template <class Callable, class Tuple, size_t... _Indices>
constexpr decltype(auto) apply_impl(Callable&& _Obj, Tuple&& _Tpl,std::index_sequence<_Indices...>)
{
using namespace std;
return std::invoke (forward<Callable>(_Obj), get<_Indices>(forward<Tuple>(_Tpl))...);
}
template <class Callable, class Tuple>
constexpr decltype(auto) apply(Callable&& Obj, Tuple&& Tpl)
{
using namespace std;
return apply_impl(forward<Callable>(Obj), forward<Tuple>(Tpl), make_index_sequence<tuple_size_v<remove_reference_t<Tuple>>>{});
}
}
int main ()
{
A a {0, 1.5};
// #define COMPILER_ERROR // enabling the define breaks the compiler
#ifdef COMPILER_ERROR
std::apply ([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, a);
#else
My::apply ([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, a);
#endif
return 0;
}
I'm trying to specialize std::tuple_size
and std::get
for a simple struct so that I can use std::apply
with it in a generic function. However, when I call std::apply
, I get a compilation error. Interestingly, if I copy the implementation of std::apply
and std::__apply_impl
into a local namespace and call that version instead, everything works fine.
Does anyone have any suggestions on what I might be doing wrong?
Here’s a minimal reproducible example.
#include <iostream>
#include <array>
#include <tuple>
#include <functional>
// Define a simple struct A with different types
struct A {
int i;
double d;
};
// Specialize std::tuple_size and std::get for A
namespace std {
template <> struct tuple_size<A> : std::integral_constant<std::size_t, 2> { }; // 3 members in A
template <std::size_t N> constexpr decltype(auto) get (const A& a) { if constexpr (N == 0) return (a.i); else if constexpr (N == 1) return (a.d); }
}
// I copied below the code for std::__apply_impl, and std::apply
namespace My {
template <class Callable, class Tuple, size_t... _Indices>
constexpr decltype(auto) apply_impl(Callable&& _Obj, Tuple&& _Tpl,std::index_sequence<_Indices...>)
{
using namespace std;
return std::invoke (forward<Callable>(_Obj), get<_Indices>(forward<Tuple>(_Tpl))...);
}
template <class Callable, class Tuple>
constexpr decltype(auto) apply(Callable&& Obj, Tuple&& Tpl)
{
using namespace std;
return apply_impl(forward<Callable>(Obj), forward<Tuple>(Tpl), make_index_sequence<tuple_size_v<remove_reference_t<Tuple>>>{});
}
}
int main ()
{
A a {0, 1.5};
// #define COMPILER_ERROR // enabling the define breaks the compiler
#ifdef COMPILER_ERROR
std::apply ([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, a);
#else
My::apply ([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, a);
#endif
return 0;
}
Share
edited Mar 4 at 0:41
康桓瑋
43.6k5 gold badges63 silver badges127 bronze badges
asked Mar 3 at 21:42
CatrielCatriel
6754 silver badges17 bronze badges
0
3 Answers
Reset to default 5The standard library is simply not designed to work with custom tuple-like types. Some other parts of the language do support them (structured bingings, and that's about it)
You are not allowed to add get()
overloads to namespace std
. It's supposed to be in the same namespace as your type, and that's where e.g. structured bindings will look for it.
Code that wishes to support user-defined tuple-like types properly needs to do using std::get;
get<I>(t)
, but the standard library doesn't do that, it simply does std::get<I>(t)
.
This is a case of overload resolution and the compiler not knowing which overload to use. If you were to copy all of the overloads, then the function My::apply
would also fail.
If all the of types are specified to std::apply
, then there will be a compile error of can't convert type A
to type std::tuple<int, double>
. I'm not sure what your goal is, but this approach won't work.
A possible direction you could take would be to make a helper function that can take custom types or tuples and always return tuples. It could then be used in calls to std::apply
. Here's an example.
#include <iostream>
#include <tuple>
#include <functional>
// Define a simple struct A with different types
struct A {
int i;
double d;
auto as_tuple() const noexcept {
return std::make_tuple(i, d);
}
};
template <typename T>
constexpr decltype(auto) as_tuple(T&& value) noexcept {
return value.as_tuple();
}
template <typename... T>
constexpr decltype(auto) as_tuple(std::tuple<T...>&& value) noexcept {
return std::forward<std::tuple<T...>>(value);
}
int main()
{
A a{ 0, 1.5 };
std::apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n"; }, as_tuple(a));
std::apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n"; }, as_tuple(std::make_tuple(1, 2, 3)));
return 0;
}
Thanks to everyone who provided an answer, and special thanks to @HolyBlackCat for pointing out the restriction on the standard library—I wasn't aware of it.
After some thought, I've come up with a solution that meets my needs and would like to share it with you. Please feel free to critique it if I’ve overlooked any potential issues or if it breaks any C++ standard restrictions.
The code below supports basic types like std::pair
, std::tuple
, and std::array
, as well as my custom specializations, to the best of my understanding.
#include <iostream>
#include <algorithm>
#include <string>
#include <array>
#include <tuple>
#include <functional>
// Define a simple struct MyStruct with two different types
struct MyStruct { int i; double d; };
struct MyOtherStruct { int i; double d; std::string s; };
// Specialize std::tuple_size for MyStruct to enable tuple-like behavior
namespace std {
// Standard allows specialization of std::tuple_size for user-defined types
template <> struct tuple_size<MyStruct> : std::integral_constant<std::size_t, 2> { }; // MyStruct has 2 members
template <> struct tuple_size<MyOtherStruct> : std::integral_constant<std::size_t, 3> { }; // MyOtherStruct has 3 members
}
namespace My {
// Support for all std::tuple-like types using std::apply
template <std::size_t N, typename StdStruct>
constexpr decltype(auto) Get(const StdStruct& a) {
return std::get<N>(a);
}
template <std::size_t N, typename StdStruct>
constexpr decltype(auto) Get(StdStruct& a) {
return std::get<N>(a);
}
template <std::size_t N, typename StdStruct>
constexpr decltype(auto) Get(StdStruct&& a) {
return std::get<N>(a);
}
// Specialization of Get for MyStruct to access its members
template <std::size_t N>
constexpr decltype(auto) Get(const MyStruct& a) {
if constexpr (N == 0)
return (a.i);
else if constexpr (N == 1)
return (a.d);
}
// Specialization of Get for MyOtherStruct to access its members
template <std::size_t N>
constexpr decltype(auto) Get(const MyOtherStruct& a) {
if constexpr (N == 0)
return (a.i);
else if constexpr (N == 1)
return (a.d);
else if constexpr (N == 2)
return (a.s);
}
// Convert a struct to a tuple using index sequence as someone else suggested
template <typename Tuple, std::size_t... I>
constexpr auto ToTupleImpl(Tuple&& t, std::index_sequence<I...>) {
return std::make_tuple(Get<I>(t)...);
}
// Public interface to convert a struct to a tuple
template <typename Tuple>
constexpr auto ToTuple(const Tuple& s) {
return ToTupleImpl(s, std::make_index_sequence<std::tuple_size<Tuple>::value>());
}
// Implementation of Apply to invoke a callable with tuple elements
template <class Callable, class Struct, size_t... Indices>
constexpr decltype(auto) Apply_impl(Callable&& Obj, Struct&& Strct, std::index_sequence<Indices...>) noexcept(
noexcept(std::invoke(std::forward<Callable>(Obj), Get<Indices>(std::forward<Struct>(Strct))...))) {
return std::invoke(std::forward<Callable>(Obj), Get<Indices>(std::forward<Struct>(Strct))...);
}
// Public interface to apply a callable to a tuple-like structure
template <class Callable, class Struct>
constexpr decltype(auto) Apply(Callable&& Obj, Struct&& Strct) noexcept(
noexcept(Apply_impl(std::forward<Callable>(Obj), std::forward<Struct>(Strct), std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Struct>>>{}))) {
return Apply_impl(std::forward<Callable>(Obj), std::forward<Struct>(Strct),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Struct>>>{});
}
}
int main() {
// Example usage of MyStruct
constexpr MyStruct ms{42, 3.14};
const MyOtherStruct mos {42, 3.14, "My other struct"};
// Apply a lambda to MyStruct converted to a tuple
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, My::ToTuple(ms));
// Apply a lambda to a std::pair
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, std::pair {2, 3});
// Apply a lambda to a std::array
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, std::array {4, 5});
// Apply a lambda directly to MyStruct
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, ms);
// Apply a lambda directly to MyOtherStruct
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, mos);
return 0;
}