Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use C++ template magic in order to pattern match on type

Tags:

c++

The desire is to have only one wrapper for all typename T which support structured bindings for example via tuple_size and tuple_element without runtime overhead(contexprs, SFINAE). There's a function encode with accepts T obj as an argument and calls encode_impl with more specific arguments and type arguments.

The article https://playfulprogramming.blogspot.com/2016/12/serializing-structs-with-c17-structured.html uses a bunch of arity functions to achieve the same result. But as far as I understand tuples provide std::tuple_size which is possible to utilize.

#include <tuple>
#include <utility>


class Aa {
    public:
        Aa(int a1_, int a2_): a1(a1_), a2(a2_) {}

        template<std::size_t N>
        decltype(auto) get() const {
            if constexpr (N == 0) return a1;
            else if constexpr (N == 1) return a2;
        }

    private:
        int a1;
        int a2;
};

class Bb {
    public:
        Bb(Aa a_, int b_): a(a_), b(b_) {}

        template<std::size_t N>
        decltype(auto) get() const {
            if constexpr (N == 0) return a;
            else if constexpr (N == 1) return b;
        }

    private:
        Aa a;
        int b;

};

namespace std {
    // Aa
    template<>
    struct tuple_size<Aa> : std::integral_constant<std::size_t, 2> {};

    template<std::size_t N>
    struct tuple_element<N, Aa> {
        using type = decltype(std::declval<Aa>().get<N>());
    };

    // Bb
    template<>
    struct tuple_size<Bb> : std::integral_constant<std::size_t, 2> {};

    template<std::size_t N>
    struct tuple_element<N, Bb> {
        using type = decltype(std::declval<Bb>().get<N>());
    };
}

template <size_t N>
using size = std::integral_constant<size_t, N>;

template<typename T>
void encode(T t) {
    encode_impl<?std::tuple_size<T>?()>(T t, ?std::tuple_size<T>);
}

template<?>
encode_impl(T t, ?) {
    std::cout << "It works";
}

The expectations of resolving the issue are the understanding of notation which must be used to compile the code snippet. Right now you can see ? in many places.

If it's not possible to do using std::tuple_size then alternative solutions are welcome. Arity function from the article for classes don't work but that's a bit different question.

like image 477
Val Avatar asked Nov 06 '22 19:11

Val


1 Answers

Are you asking how to apply the return values of some_type::get<i>() as the arguments of a function?

Off the top of my head you would write encode and encode_impl something like this:

template<typename T>
void encode(T const& t, std::ostream& os)
{
  encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
}

template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
  constexpr auto last = sizeof...(I) - 1;
  os << "{ ";
  [[maybe_unused]] int const temp[] = 
    { ((os << t.template get<I>() << (I != last ? ", " : " ")), 0)... };
  os << "}" << std::endl;
}

This disgusting looking pack expansion in encode_impl is using the comma operator just to force evaluation of its left hand operand and discard the result, then evaluate the literal 0 and store it in the dummy array temp. The pack expansion is used to initialize an array so that the arguments are evaluated in the correct order (left to right).

UPDATE: Okay so I think what you want is to make your own type trait test is_tuple_like that returns true if the type T is "tuple-like" which just requires that the expression std::tuple_size<T>::value is well formed and the expression declval<T&>().template get<std::size_t(0)>() is well formed. From there you can write a function that prints the elements of "tuple-like" types and if any of those elements are tuple-like, print their elements, recursively. Here's what I came up with:

template
<
  typename T,
  typename tp_enabled =
    std::void_t
    <
      decltype(std::tuple_size<T>::value),
      decltype(std::declval<T&>().template get<std::size_t(0)>())
    >
>
constexpr auto
is_tuple_like(int const)noexcept->bool
{
  return true;
}

template
<typename T, typename tp_arg>
constexpr auto
is_tuple_like(tp_arg const)noexcept->bool
{
  return false;
}

template<typename T>
auto encode(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>(0)>
{
  encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
  os << std::endl;
}

template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<!is_tuple_like<T>(0)>
{
  os << t << (is_last ? " " : ", ");
}

template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>(0)>
{
  encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
  os << (is_last ? " " : ", ");
}

template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
  constexpr auto last = sizeof...(I) - 1;
  os << "{ ";
  [[maybe_unused]] int const temp[] =
    { (encode_one<I == last>(t.template get<I>(), os), 0)... };
  os << "}";
}

int main () {
    auto a = Aa(1, 1);
    encode(a, std::cout);
    auto b = Bb(a, 1);
    encode(b, std::cout);
    return 0;
}

OUTPUT:

{ 1, 1 }
{ { 1, 1 }, 1 }

UPDATE 2: So it turns out the above implementation of is_tuple_like compiles fine on GCC and the latest version of Clang (8.0.0) but fails to compile on Clang 7.0.0 so here is a version that works on Clang 7.0.0 it uses a variable template instead of a function template:

#include <tuple>
#include <utility>
#include <iostream>

class Aa {
    public:
        Aa(int a1_, int a2_): a1(a1_), a2(a2_) {}

        template<std::size_t N>
        decltype(auto) get() const {
            if constexpr (N == 0) return a1;
            else if constexpr (N == 1) return a2;
        }

    private:
        int a1;
        int a2;
};

class Bb {
    public:
        Bb(Aa a_, int b_): a(a_), b(b_) {}

        template<std::size_t N>
        decltype(auto) get() const {
            if constexpr (N == 0) return a;
            else if constexpr (N == 1) return b;
        }

    private:
        Aa a;
        int b;
};


namespace std {
    // Aa
    template<>
    struct tuple_size<Aa> : std::integral_constant<std::size_t, 2> {};

    template<std::size_t N>
    struct tuple_element<N, Aa> {
        using type = decltype(std::declval<Aa>().get<N>());
    };

    // Bb
    template<>
    struct tuple_size<Bb> : std::integral_constant<std::size_t, 2> {};

    template<std::size_t N>
    struct tuple_element<N, Bb> {
        using type = decltype(std::declval<Bb>().get<N>());
    };
}

template
<typename T, typename tp_enabled = std::void_t<>>
constexpr bool is_tuple_like = false;

template
<typename T>
constexpr bool 
is_tuple_like
<
  T, 
  std::void_t
  <
    decltype(std::tuple_size<T>::value),
    decltype(std::declval<T&>().template get<std::size_t(0)>())
  >
> = true;

template<typename T>
auto encode(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>>
{
  encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
  os << std::endl;
}

template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<!is_tuple_like<T>>
{
  os << t << (is_last ? " " : ", ");
}

template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>>
{
  encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
  os << (is_last ? " " : ", ");
}

template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
  constexpr auto last = sizeof...(I) - 1;
  os << "{ ";
  [[maybe_unused]] int const temp[] =
    { (encode_one<I == last>(t.template get<I>(), os), 0)... };
  os << "}";
}

int main () {
    auto a = Aa(1, 1);
    encode(a, std::cout);
    auto b = Bb(a, 1);
    encode(b, std::cout);
    return 0;
}
like image 93
Mike Avatar answered Nov 14 '22 21:11

Mike