Reflective programming

In preperation for c++17 structured bindnings, been playing around with some reflection stuff (so sticking to tuples/pairs). Just wondering if there is any way to avoid writing the SelectiveReflectiveOperation function (so only keep ReflectiveOperation). Macro hackery would be ok.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <string>
#include <iostream>
#include <tuple>
#include <type_traits>
#include <vector>
#include <algorithm>

template<typename T, typename F, template<typename> class Tester>
struct ReflectiveOperator
{
    static constexpr size_t TerminalCase = std::tuple_size<std::decay_t<T>>::value - 1;

    template<size_t Index>
    static std::enable_if_t<Index != TerminalCase, size_t> Op(T&& tuple, const F& func)
    {
        return PerformOp<Index>(std::forward<T>(tuple), func) + Op<Index + 1>(std::forward<T>(tuple), func);
    }

    template<size_t Index>
    static std::enable_if_t<Index == TerminalCase, size_t> Op(T&& tuple, const F& func)
    {
        return PerformOp<TerminalCase>(std::forward<T>(tuple), func);
    }

private:
    template<size_t Index>
    static std::enable_if_t<Tester<decltype(std::get<Index>(std::declval< T >()))>::value, size_t> PerformOp(T&& tuple, const F& func)
    {
        func(std::get<Index>(std::forward<T>(tuple)));
        return 1; //1 field was changed
    }

    template<size_t, typename... Args>
    static size_t PerformOp(Args&&...) { return 0; }
};

template<typename>
struct DefaultTester
{
    constexpr static bool value = true;
};

template<typename T>
class CanDivideByInt
{
    template<typename = decltype(std::declval<T>() /= int())>
    static constexpr bool test(int) { return true; }

    static constexpr bool test(...) { return false; }
public:
    constexpr static bool value = test(int());
};

template<typename T, typename F>
static size_t ReflectiveOperation(T&& tuple, const F& func)
{
    return ReflectiveOperator<T, F, DefaultTester>::template Op<0>(std::forward<T>(tuple), func);
}

template<template<typename> class Tester, typename T, typename F>
static size_t SelectiveReflectiveOperation(T&& tuple, const F& func)
{
    return ReflectiveOperator<T, F, Tester>::template Op<0>(std::forward<T>(tuple), func);
}

template<typename S, typename T>
class is_streamable
{
    template<typename SS, typename TT>
    static constexpr decltype(std::declval<SS&>() << std::declval<TT>(), bool()) test(int) { return true; };

    template<typename...> static constexpr auto test(...) { return false; }

public:
    static constexpr bool value = test<S, T>(int());
};

template<typename T>
using CanCout = is_streamable<std::decay_t<decltype(std::cout)>, T>; //g++ hack/workaround

int main()
{
    std::tuple<double, int, char, std::string, float, std::vector<int>> tuple{ 11, 22, '!' * 2, "hello", 0.2f,{ 7 } };

    size_t numberChanged = SelectiveReflectiveOperation<CanDivideByInt>(tuple, [](auto& x) { x /= 2; });
    std::cout << "Number of changes: " << numberChanged << std::endl;
    SelectiveReflectiveOperation<CanCout>(tuple, [](auto& x) { std::cout << x << ' '; });
    std::cout << std::endl << "Sizes: ";
    ReflectiveOperation(tuple, [](auto& x) { std::cout << sizeof(x) << ' '; });
    return 0;
}
Last edited on
Something like this, perhaps (perfect forwarding of tuples elided for brevity):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <type_traits>
#include <functional>
#include <tuple>
#include <string>

template < typename FN, typename... ARGS >
typename std::enable_if< std::is_invocable_v< FN&&, ARGS&&...> >::type
invoke_if_invokable( FN&& fn, ARGS&&... args )
{ std::forward<FN>(fn)( std::forward<ARGS>(args)...) ; }

template < typename FN, typename... ARGS >
typename std::enable_if< !std::is_invocable_v< FN&&, ARGS&&...> >::type
invoke_if_invokable( FN&&, ARGS&&... ) {}

// ----------------------------------------------------------------

template < std::size_t N = 0, typename FN, typename... T >
typename std::enable_if< N == sizeof...(T) >::type
for_each_invokable_in( const std::tuple<T...>&, FN ) {}

template < std::size_t N = 0, typename FN, typename... T >
typename std::enable_if< ( N < sizeof...(T) ) >::type
for_each_invokable_in( std::tuple<T...>& tup, FN fn )
{
    invoke_if_invokable( fn, std::get<N>(tup) );
    for_each_invokable_in<N+1>( tup, fn );
}

template < std::size_t N = 0, typename FN, typename... T >
typename std::enable_if< ( N < sizeof...(T) ) >::type
for_each_invokable_in( const std::tuple<T...>& tup, FN fn )
{
    invoke_if_invokable( fn, std::get<N>(tup) );
    for_each_invokable_in<N+1>( tup, fn );
}

// ------------------------------------------------------------------------

struct print_it
{
    template < typename T >
       typename std::enable_if< sizeof( std::cout << std::declval<T>() ) >::type
          operator() ( const T& a ) const { std::cout << a << '\n' ; }
};

struct triple_it
{
    template < typename T >
       typename std::enable_if< !std::is_const_v<T> && sizeof( std::declval<T>() + std::declval<T>() ) >::type
          operator() ( T& a ) const { a = a + a + a ; }
};

// ------------------------------------------------------------------------------

int main() // minimal test driver
{
    struct A{}; struct B{};
    auto tup = std::tuple { 12345, 678.9, A{}, B{}, std::string{"hello "}, "world!" } ;

    for_each_invokable_in( tup, print_it{} ) ; // print int, double, string and c-string
    std::cout << "------------------\n" ;

    for_each_invokable_in( tup, triple_it{} ) ; // triple int, double and string
    for_each_invokable_in( tup, print_it{} ) ;
    std::cout << "------------------\n" ;

    std::tuple< int, const int, double, const double, std::string > tup2 { 8, 8, 8, 8, "8" } ;
    for_each_invokable_in( tup2, print_it{} ) ;
    std::cout << "------------------\n" ;
    for_each_invokable_in( tup2, triple_it{} ) ; // triple the modifiable values
    for_each_invokable_in( tup2, print_it{} ) ;
    std::cout << "------------------\n" ;

    const auto ctup = tup2 ;
    for_each_invokable_in( ctup, triple_it{} ) ; // do nothing
    for_each_invokable_in( ctup, print_it{} ) ;
}

http://coliru.stacked-crooked.com/a/01a25acf0260dbcf
Unfortunately I don't have c++17, but looks promising. There is still the duplicate information in places though (bold for me is duplicate):

1
2
3
4
5
6
struct print_it
{
    template < typename T >
       typename std::enable_if< sizeof( std::cout << std::declval<T>() ) >::type
          operator() ( const T& a ) const { std::cout << a << '\n' ; }
};


I've improved my answer a little bit, added TripleIt and showed it works for const tuple elements as well as a const tuple. However some macro hackery was used. Due to inability to inline templated classes inside methods, I don't think macro hackery can take it much further unfortunately. Maybe something with the using keyword + a macro, but that seems about it at the moment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <type_traits>
#include <iostream>
#include <tuple>
#include <string>
#include <vector>

template<typename T, typename F, template<typename> class Tester>
struct ReflectiveOperator
{
    static constexpr size_t TerminalCase = std::tuple_size<std::decay_t<T>>::value - 1;

    template<size_t Index>
    static std::enable_if_t<Index != TerminalCase, size_t> Op(T&& tuple, const F& func)
    {
        return PerformOp<Index>(std::forward<T>(tuple), func) + Op<Index + 1>(std::forward<T>(tuple), func);
    }

    template<size_t Index>
    static std::enable_if_t<Index == TerminalCase, size_t> Op(T&& tuple, const F& func)
    {
        return PerformOp<TerminalCase>(std::forward<T>(tuple), func);
    }

private:
    template<size_t Index>
    static std::enable_if_t<Tester<decltype(std::get<Index>(std::declval< T >()))>::value, size_t> PerformOp(T&& tuple, const F& func)
    {
        func(std::get<Index>(std::forward<T>(tuple)));
        return 1; //1 field was changed
    }

    template<size_t, typename... Args>
    static size_t PerformOp(Args&&...) { return 0; }
};

template<typename T, typename F>
static size_t ReflectiveOperation(T&& tuple, const F& func)
{
    return ReflectiveOperator<T, F, DefaultTester>::template Op<0>(std::forward<T>(tuple), func);
}

template<template<typename> class Tester, typename T, typename F>
static size_t SelectiveReflectiveOperation(T&& tuple, const F& func)
{
    return ReflectiveOperator<T, F, Tester>::template Op<0>(std::forward<T>(tuple), func);
}

#define TestType(Name, X) template<typename T> \
class Name  \
{ \
    template<typename = decltype(X)> \
    static constexpr bool test(int) { return true; } \
    static constexpr bool test(...) { return false; } \
public: \
    constexpr static bool value = test(int()); \
};

TestType(DefaultTester, int());
TestType(CanDivideByInt, std::declval<T>() /= int());
TestType(CanCout, std::cout << std::declval<T>());
TestType(TripleIt, std::declval<T>() = std::declval<T>() + std::declval<T>() + std::declval<T>());

int main()
{
    std::tuple<double, int, char, std::string, const float, std::vector<int>> tuple{ 11, 22, '!' * 2, "hello", 0.2f, { 7 } };
    size_t numberChanged = SelectiveReflectiveOperation<CanDivideByInt>(tuple, [](auto& x) { x /= 2; });
    SelectiveReflectiveOperation<CanDivideByInt>(const_cast<const decltype(tuple)&>(tuple), [](auto& x) { x /= 2; }); //does nothing
    std::cout << "Number of changes: " << numberChanged << std::endl;
    SelectiveReflectiveOperation<CanCout>(tuple, [](auto& x) { std::cout << x << ' '; });
    std::cout << std::endl << "Sizes: ";
    ReflectiveOperation(tuple, [](auto& x) { std::cout << sizeof(x) << ' '; });
    numberChanged = SelectiveReflectiveOperation<TripleIt>(tuple, [](auto& x) { x = x + x + x; });
    std::cout << std::endl << "Number of changes: " << numberChanged << std::endl;
    SelectiveReflectiveOperation<CanCout>(tuple, [](auto& x) { std::cout << x << ' '; });
}


Note: Compiled on MSVC, GCC does not like the CanCout for some reason. Anyway really I would just love this to be the main method, with no other boiler plate beyond the stuff needed to get ReflectiveOperation going:

1
2
3
4
5
6
7
int main()
{
    std::tuple<double, int, char, std::string, const float, std::vector<int>> tuple{ 11, 22, '!' * 2, "hello", 0.2f,{ 7 } };
    ReflectiveOperation(tuple, [](auto& x) {x /= 2; });
    ReflectiveOperation(const_cast<const decltype(tuple)&>(tuple), [](auto& x) {x /= 2; }); //does nothing
    //etc.
}


Maybe macros + your solution might work? Can define the structs inline.
Last edited on
> Compiled on MSVC, GCC does not like the CanCout for some reason.

Compilers also won't like use of the expression x /= 2 ; where x is an object of a const-qualified type.
http://coliru.stacked-crooked.com/a/ac1247c9cb751ea7


> I don't have c++17

Nuwen has GCC 7.2 (and Boost 1.65.1 as a bonus); however the GNU libstdc++ is quite some distance away from C++17 conformance. https://nuwen.net/mingw.html
Registered users can post here. Sign in or register to post.