3

I want to make a traits class apply to a type as well as its descedants. Is this possible?

template <typename E>
struct Garble {
};

template <typename T>
struct wooble_traits;

template <typename E>
struct wooble_traits<Garble<E>> {
  typedef E elem_type;
};

struct IntGarble : public Garble<int> {
};

typedef typename wooble_traits<IntGarble>::elem_type IGType;
//Error, wooble_traits<IntGarble> has no definition.

Is there some way to instead say (borrowing and abusing Java notation):

template <typename E>
struct wooble_traits<? extends Garble<E>> {
  typedef E elem_type
};

typedef typename wooble_traits<IntGarble>::elem_type IGType;
//Fine, IGType is an alias for int

Note:
Attempting to adapt Dyp's solution to my example doesn't work since Garble takes a type parameter. There doesn't seem to be anywhere to infer that parameter.

#include <boost/type_traits/is_base_of.hpp>

template <typename T, typename C = void>
struct wooble_traits;

template <typename T, typename E>
struct wooble_traits<T, typename boost::is_base_of<Garble<E>, T>::type> {
  typedef E elem_type;
};

in gcc-4.6 this produces:

g++ -I/usr/include/boost/utility -I/usr/include/boost/type_traits -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"main.d" -MT"main.d" -o "main.o" "../main.cpp"
../main.cpp:15:8: error: template parameters not used in partial specialization:
../main.cpp:15:8: error:         ‘E’
make: *** [main.o] Error 1

which is understandable since there's no way for GCC to know the value of E.

dspyz
  • 5,280
  • 2
  • 25
  • 63
  • @Jefffrey: eg `IntGarble`. a subclass / derived class. – Karoly Horvath Mar 24 '14 at 22:46
  • By descendant I mean derived class. A traits class makes information about a type available at compile-time. wooble_traits exposes a wooble's elem_type to be used at compile-time. When viewed as a wooble, a Garble has elem_type same as its template argument. All derived classes from some Garble (including IntGarble) have the same elem_type as their Garble ancestor (in the case of IntGarble, this type is int). – dspyz Mar 24 '14 at 22:48

1 Answers1

3

For base classes:

#include <type_traits>

template<bool b>
using stdbool_t = std::integral_constant<bool, b>;

template<class T, class U = std::true_type>
struct trait
    : std::false_type
{};

struct foo {};
struct bar : foo {};

template<class T>
struct trait<T, stdbool_t<std::is_base_of<foo, T>{}>>
    : std::true_type
{};


#include <iostream>

int main()
{
    std::cout << std::boolalpha;

    std::cout << trait<int>::value << "\n";
    std::cout << trait<foo>::value << "\n";
    std::cout << trait<bar>::value << "\n";
}

For some reason, specializing on non-type template parameters isn't allowed when the expression depends on (the previous) type parameters.

g++4.8.2 fails to compile this btw (ICE), but it works fine with clang++3.5


Here's an alternative version that compiles on both compilers:

template<class T, class = void>
struct trait
    : std::false_type
{};

struct foo {};
struct bar : foo {};

template<class T>
struct trait<T, typename std::enable_if<std::is_base_of<foo, T>{}>::type>
    : std::true_type
{};

The enable_if isn't necessary, actually. A std::conditional would work as well, but the enable_if is shorter here.


If the base is a template specialization, we can use Jarod42's solution (I've modified it a bit):

template<template<class...> class T, class U>
struct is_base_template_of
{
private:
    template<class... V>
    static auto test(const T<V...>&)
        -> decltype(static_cast<const T<V...>&>(std::declval<U>()),
                    std::true_type{});

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

public:
    static constexpr bool value =
        decltype(is_base_template_of::test(std::declval<U>()))::value;
};


template<class T>
struct trait<T,
             typename std::enable_if<is_base_template_of<foo, T>::value>::type>
    : std::true_type
{};

Note: this only works for public inheritance (and I think it has some further restrictions: virtual and some cases of multiple inheritance shouldn't work either).


Here's a version of the same type trait in "C++03 + boost" style:

template<template<class> class T, class U>
struct is_base_template_of
{
private:
    typedef char false_type;
    typedef char(& true_type)[2];

    template<class V>
    static true_type test(const T<V>*);
    static false_type test(...);

public:
    static const bool value =
       (   sizeof(test(std::declval<typename std::remove_reference<U>::type*>()))
        == sizeof(true_type));
};
Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • 1
    Ok, but how about when foo takes a template parameter (as in my example). How do you do it then? (without forcing the client to know the type of that parameter) – dspyz Mar 25 '14 at 07:01
  • @dspyz Oops, sry, I've simplified the test case too much. Added a solution where the base class is a template specialization. – dyp Mar 25 '14 at 14:26
  • Ok, this looks good, but the project I'm working on doesn't use C++11 (as you might have guessed from my boost-based examples). Is there some way to do this without decltype? – dspyz Mar 25 '14 at 18:11
  • Oh, boost has a TYPEOF macro which looks like it does the same thing. I'll test it to see if it works. – dspyz Mar 25 '14 at 18:18
  • @dspyz The `decltype` part just makes SFINAE a bit more convenient. I'll try to replace it with old-fashioned SFINAE. – dyp Mar 25 '14 at 18:21
  • @dspyz Try the version in "C++03 + boost" style – dyp Mar 25 '14 at 18:36
  • I replaced std::declval::type*> with (U*) 0. It compiles, but now I can't figure out how to retrieve the template parameter (as you remember, I wanted to make traits::elem_type be an alias for E (V in your example)). But by using is_base_template_of, that information gets lost. – dspyz Mar 25 '14 at 19:04
  • `declval` can be easily implemented in C++03: `template T declval();` (the difference to C++11 is that the latter return `T&&`). Retrieving the template parameter can be done with a deduction very similar to the one used in my post, I'll add an example. – dyp Mar 25 '14 at 19:38
  • @dspyz [`boost` also has a `declval`](http://www.boost.org/doc/libs/1_55_0/libs/utility/doc/html/declval.html). The example isn't as easy as I thought, since I may not use `decltype` :( Inside the `test` function, the template parameter is known, so you can always write a similar function and inside use the template parameter. Not sure if you can get it *out* of the function, though. – dyp Mar 25 '14 at 19:48
  • Well, this has gotten far more complicated and involved than I'm willing to tolerate any way. I wanted to do this without any change to existing classes, but it's not worth the extra effort at this point. I'm just going to add a typedef E elem_type directly to the classes themselves (rather than having a separate traits class). They're already so bloated with code that doesn't belong, 2 or 3 extra lines might even go unnoticed. I'll mark this as the accepted answer since no one else seems to have any other ideas and I learned a lot from it. – dspyz Mar 26 '14 at 14:57