How to find if a template argument has a nested structure?

Hi,

Given a template:

1
2
3
template<typename MetaFn, typename ...Args>
struct is_metafunction_class : ??
{};


I want to find if MetaFn has a nested apply structure. An example would be:

1
2
3
4
5
6
7
8
struct plus_f
{
	template<class T1, class T2>
	struct apply
	{
		typedef typename mpl::plus<T1, T2>::type type;
	};
};


Thus, this code would return true:

1
2
constexpr bool _has_apply = is_metafunction_class<plus_f, mpl::int_<3>, mpl::int_<6>>::value;
static_assert( _has_apply == true);


Any thoughts?

Regards,
Juan Dent
Last edited on
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
#include <iostream>
#include <type_traits>

template < typename T, typename... ARGS > struct has_template_apply
{
     template < typename U >
     static std::true_type test( U, typename U::template apply<ARGS...>* = nullptr ) ;

     static std::false_type test(...) ;

     using type = decltype( test( std::declval<T>() ) ) ;
     static constexpr bool value = type{} ;
};

struct A {};
struct B { template < typename, typename > struct apply{}; };
struct C { template < typename, typename... > struct apply{}; };

int main()
{
    std::cout << std::boolalpha
              << has_template_apply< A, char, int >::value << '\n' // false

              << has_template_apply< B, char, int >::value << '\n' // true
              << has_template_apply< B, char, int, double >::value << '\n' // false

              << has_template_apply< C, char, int >::value << '\n' // true
              << has_template_apply< C, char, int,double >::value << '\n' ;// true
}

http://coliru.stacked-crooked.com/a/dd703aafcebe0510
As always JLBorges to the rescue !
One thing only... has_template_apply<B,char, int>::value is returning true yet B's apply is not a variadic template. Is there a way to make B always return false since it's apply is not variadic ?

Regards,
Juan Dent
> yet B's apply is not a variadic template.
> Is there a way to make B always return false since it's apply is not variadic ?

What would be the practical use case for this? Isn't all that we need is to check is if there is an apply<> available for the template arguments that are of interest to us?

As an academic exercise, we can check for presence of a variadic template by checking if the template can be instantiated with an absurdly large number (say 65536) of template arguments. This assumes that no one in their right senses would attempt to write a non-variadic template with many thousands of template parameters.

Something like this:
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
#include <iostream>
#include <type_traits>

template < typename... ARGS > struct pack
{ static constexpr std::size_t PACK_SIZE = sizeof...(ARGS) ; } ;

template < unsigned int N, typename... ARGS > struct repeat
{ using type = typename repeat< N-1, ARGS..., ARGS... >::type ; };

template < typename... ARGS > struct repeat< 0, ARGS...>
{ using type = pack<ARGS...> ; };

template < typename T, unsigned int N = 16 > // check for apply with 2^N template parameters
struct has_variadic_template_apply
{
     template < typename U, typename... ARGS >
     static std::true_type test( U, pack<ARGS...>, typename U::template apply<ARGS...>* = nullptr ) ;

     static std::false_type test(...) ;

     using type = decltype( test( std::declval<T>(), typename repeat<N,void>::type{} ) ) ;
     static constexpr bool value = type{} ;
};

struct A {};
struct B { template < typename, typename > struct apply{}; };
struct C { template < typename... > struct apply{}; };

int main()
{
    std::cout << "pack size: " << repeat<16,void>::type::PACK_SIZE << '\n' // 65536
              << std::boolalpha
              << has_variadic_template_apply<A>::value << '\n' // false
              << has_variadic_template_apply<B>::value << '\n' // false
              << has_variadic_template_apply<C>::value << '\n' ;// true
}

http://coliru.stacked-crooked.com/a/f55a784db538fcd0
http://rextester.com/VJBVDU11600

Note that if N is too large (the pack size == 2^N), the compiler may run out of memory or some implementation limits may be breached.
To distinguish b/w types that have nested class template (NCT) apply of either the variadic or non-variadic flavor and those that don't have the NCT apply:
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
# include <iostream>
# include <type_traits>

struct A{};
struct B{};
struct C{};

template <typename T> struct has_apply /*: public std::false_type*/ {static const bool value = false;};
template <typename T> struct is_variadic /*: public std::false_type*/ {static const bool value = false;};

template <> struct has_apply <B>  {static const bool value = true;};
template <> struct is_variadic <B>  {static const bool value = true;};
template <> struct has_apply <C> {static const bool value = true;};

template <typename T, typename std::enable_if<(has_apply<T>::value && !(is_variadic<T>::value)), T>::type* = nullptr>
 void print(T& t)
 {std::cout << "nested && non-variadic\n";}

template <typename T, typename std::enable_if<(has_apply<T>::value && is_variadic<T>::value), T>::type* = nullptr >
 void print(T& t)
 {std::cout << "nested && variadic\n";}

template<typename T, typename std::enable_if<!(has_apply<T>::value), T>::type* = nullptr>
void print(T& t)
{std::cout << "!(nested && variadic) \n";}


int main()
{
    A a{};
    B b{};
    C c{};
    std::cout << "type A: "; print(a);
    std::cout << "type B: "; print(b);
    std::cout << "type C: "; print(c);
}
Hi JLBorges,

The question for this post might have been slightly inaccurate. I think I better clarify what it is I am trying to do:

In Boost.MPL, there is a metafunction lambda which returns a metafunction class for a given metafunction when such is not already a metafunction class, but returns the original if it is already a metafunction class.

Thus what I am trying to do is to write my own lambda.

Recall a metafunction class is a class with a publicly accessible nested metafunction called apply.


I think my intent is clear now.

Your input will be greatly appreciated!!

Regards,
Juan Dent
MPL lambda transforms a place holder expression into its corresponding metafunction class, and leaves a non placeholder expression unchanged.

Are you talking about quote / quoten ? http://www.boost.org/doc/libs/1_64_0/libs/mpl/doc/refmanual/quote.html
Hi,

It seems quote does the same thing as lambda. The real issue is how to detect if a metafunction is already a metafunction class (i.e. has a nested apply) so it does not need to be changed.

The problem with the above suggestions was that the template substitution is too detailed. For instance, if the metafunction is mpl::plus<>, then we cannot test if apply works by passing any 2 types (e.g. int, int) because plus expects specific kinds of types (in this case wrapped integral constants like mpl::int_<6>).

So if we pass a metafunction to a struct whose job is to determine if it has an apply, it will fail to respond true if the types passed to apply are not substitutable (like the int_ above). Thus, false may be received due to 2 different reasons: 1) in the cases where there is no nested apply (which is what is intended) or 2) when there IS a nested apply but the types passed to it do not substitute correctly (e.g. passing int,int instead of int_<5>,int_<8>).

am I being clear?

Regards,
Juan Dent
> It seems quote does the same thing as lambda.

No.

MPL lambda internally invokes quote in addition to transforming place holder expressions into their corresponding metafunction classes.

It is MPL quote that wraps a metafunction to produce a metafunction class.


> So if we pass a metafunction to a struct whose job is to determine if it has an apply,
> it will fail to respond true if the types passed to apply are not substitutable

MPL quote (and therefore MPL lambda) does not check for anything other than presence or absence of a nested type called apply. It blithely ignores whether the nested type apply is a templated type or not, let alone what are the valid template parameters if it happens to be a templated type.

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
#include <boost/mpl/lambda.hpp>
#include <type_traits>
#include <typeinfo>
#include <string>
#include <iostream>

#ifdef __GNUG__ // GNU or compatible : we have to demangle std::type_info::name()

#include <cxxabi.h>

std::string name( const std::type_info& tinfo )
{
    static constexpr std::size_t MAX_LEN = 8192 ;
    char buffer[ MAX_LEN ] ;

    std::size_t size = MAX_LEN ;
    int status ;
    __cxxabiv1::__cxa_demangle( tinfo.name(), buffer, std::addressof(size),
                                std::addressof(status) ) ;

    return status==0 ? buffer : "__cxa_demangle error" ;
}

#else // hopefully microsoft which plays nice with std::type_info::name()

std::string name( const std::type_info& tinfo ) { return tinfo.name() ; }

#endif // __GNUG__

namespace one { struct A { using apply = void ; }; }

int main()
{
    using lambdas_nested_type = boost::mpl::lambda<one::A>::type ;
    using lambdas_nested_types_applied_type = lambdas_nested_type::apply ;

    static_assert( std::is_same< lambdas_nested_type, one::A >::value,
                   "lambda's type is expected to be one::A" ) ;

    static_assert( std::is_same< lambdas_nested_types_applied_type, void >::value,
                   "lambda's nested type's applied_type should be void" ) ;

    std::cout << "lambdas_nested_type is '" << name( typeid(lambdas_nested_type) ) // 'one::A'
              << "'\nlambdas_nested_types_applied_type is '"
              << name( typeid(lambdas_nested_types_applied_type) ) << "'\n" ; // 'void'
}

lambdas_nested_type is 'struct one::A'
lambdas_nested_types_applied_type is 'void'

http://rextester.com/NEHJ13564
Last edited on
Hi!

To find if a type T has a nested struct apply the following code works (but...)

1
2
3
4
5
template<typename T, typename = void_t<>>
struct has_apply_member : false_type {};

template<typename T>
struct has_apply_member<T, void_t< typename T::apply> > : true_type {};



However, when the nested struct apply is a template class (regular or variadic) then the above code fails and testing by trying to instantiate apply has the problem that the parameters must satisfy the *concepts* of the particular apply.

Taking the following struct M, then the instantiation will work iif both template parameters are supplied AND are integral constant wrappers and so fit the concept of this particular template class apply. This problem I have not found yet how to solve, which then leaves the larger problem unanswered "how can I know if a type T has a nested struct apply where that apply may or may not be a template".

1
2
3
4
5
6
struct M
{
    template<typename N1, typename N2>
    struct apply : mpl::plus<N1,N2>
    {};
};


Regards,
Juan Dent
> "how can I know if a type T has a nested struct apply where that apply may or may not be a template"

Haven't we already gone through this?

We can check if a type has an accessible nested non-template apply.
We can check if a type has an accessible nested template apply with a specific set of template arguments.
We can check if a type has an accessible nested variadic template apply.

Don't these cover all bases?

Putting it all together:

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
92
#include <iostream>
#include <type_traits>

// check if T has a nested apply that can be instantiated with specified template arguments
template < typename T, typename... ARGS > struct has_specified_template_apply
{
     template < typename U >
     static std::true_type test( U, typename U::template apply<ARGS...>* = nullptr ) ;

     static std::false_type test(...) ;

     using type = decltype( test( std::declval<T>() ) ) ;
     static constexpr bool value = type{} ;
};

// check if T has a non-template apply
template < typename T > struct has_non_template_apply
{
     template < typename U >
     static std::true_type test( U, typename U::apply* = nullptr ) ;

     static std::false_type test(...) ;

     using type = decltype( test( std::declval<T>() ) ) ;
     static constexpr bool value = type{} ;
};

namespace detail
{
    template < typename... ARGS > struct pack
    { static constexpr std::size_t PACK_SIZE = sizeof...(ARGS) ; } ;

    template < unsigned int N, typename... ARGS > struct repeat
    { using type = typename repeat< N-1, ARGS..., ARGS... >::type ; };

    template < typename... ARGS > struct repeat< 0, ARGS...>
    { using type = pack<ARGS...> ; };
}

// check if T has a nested variadic template apply (with template type parameters)
template < typename T, unsigned int N = 16 > // check for apply with 2^N template parameters
struct has_variadic_template_apply
{
     template < typename U, typename... ARGS >
     static std::true_type test( U, detail::pack<ARGS...>, typename U::template apply<ARGS...>* = nullptr ) ;

     static std::false_type test(...) ;

     using type = decltype( test( std::declval<T>(), typename detail::repeat<N,void>::type{} ) ) ;
     static constexpr bool value = type{} ;
};

template < typename T > void test()
{
    std::cout << typeid(T).name() << '\n'<< std::boolalpha
              << "has_non_template_apply? " << has_non_template_apply<T>::value

              << "\nhas_specified_template_apply<T,int,char> ? "
              << has_specified_template_apply<T,int,char>::value
              << "\nhas_specified_template_apply<T,int,char,double,void> ? "
              << has_specified_template_apply<T,int,char,double,void>::value

              << "\nhas_variadic_template_apply? " << has_variadic_template_apply<T>::value
              << "\n\n" ;
}

template < typename... > struct type_list ;
template <> struct type_list<> { static void test() {} } ;
template < typename FIRST, typename... REST > struct type_list< FIRST, REST... >
{ static void test() { ::test<FIRST>() ; type_list<REST...>::test() ; } };

struct one {};
struct two { using apply = void ; };
struct three { template < typename, typename > struct apply{}; };
struct four { template < typename, typename... > struct apply{}; };
struct five
{
    template < typename A, typename B,
               typename = typename std::enable_if< std::is_pointer<B>::value >::type >
    struct apply {};
};

int main()
{
     type_list< one, two, three, four, std::is_pod<int> >::test() ;

     std::cout << "is there a five::apply< int, void* > ? " 
               << has_specified_template_apply< five, int, void* >::value << '\n' // true
               
               << "is there a five::apply< int, double& > ? "
               << has_specified_template_apply< five, int, double& >::value << '\n' ; // false
}

3one
has_non_template_apply? false
has_specified_template_apply<T,int,char> ? false
has_specified_template_apply<T,int,char,double,void> ? false
has_variadic_template_apply? false

3two
has_non_template_apply? true
has_specified_template_apply<T,int,char> ? false
has_specified_template_apply<T,int,char,double,void> ? false
has_variadic_template_apply? false

5three
has_non_template_apply? false
has_specified_template_apply<T,int,char> ? true
has_specified_template_apply<T,int,char,double,void> ? false
has_variadic_template_apply? false

4four
has_non_template_apply? false
has_specified_template_apply<T,int,char> ? true
has_specified_template_apply<T,int,char,double,void> ? true
has_variadic_template_apply? true

NSt3__16is_podIiEE
has_non_template_apply? false
has_specified_template_apply<T,int,char> ? false
has_specified_template_apply<T,int,char,double,void> ? false
has_variadic_template_apply? false

is there a five::apply< int, void* > ? true
is there a five::apply< int, double& > ? false

http://coliru.stacked-crooked.com/a/a6fde594da43dab4
http://rextester.com/FRZ89646
My apologies JLBorges - it wasn't clear to me. I will take a deep look into this.

Thank you again and always!

Regards,
Juan
Topic archived. No new replies allowed.