c++ - Specializing std::tuple_size and std::get for std::apply - Compilation Error - Stack Overflow

admin2025-04-19  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;
}

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
Add a comment  | 

3 Answers 3

Reset to default 5

The 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;
}
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745070622a283255.html

最新回复(0)