2

I have code with the following structure:

template <typename T>
struct Foo
{
  struct Bar
  {
    int data;
  };
};

I want to write metafunctions which will tell me if a type is either Foo or Bar. The first one is easy:

template <typename T>
struct is_foo : boost::mpl::false_
{};

template <typename T>
struct is_foo<Foo<T> > : boost::mpl::true_
{};
...
BOOST_MPL_ASSERT(( is_foo<Foo<int> > ));
BOOST_MPL_ASSERT_NOT(( is_foo<int> ));

However, the same approach does not work for Bar:

template <typename T>
struct is_bar : boost::mpl::false_
{};

template <typename T>
struct is_bar<typename Foo<T>::Bar> : boost::mpl::true_
{};

This code is rejected by the compiler. GCC says:

main.cpp:38:8: error: template parameters not used in partial specialization:
main.cpp:38:8: error:         ‘T’

Oddly, clang will compile the code, but it issues a warning and the metafunction does not work (always false):

main.cpp:38:8: warning: class template partial specialization contains a template parameter that can not be deduced;
      this partial specialization will never be used
struct is_bar<typename Foo<T>::Bar> : boost::mpl::true_
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:37:20: note: non-deducible template parameter 'T'
template <typename T>
                   ^

Is there a workaround for this issue? A c++11-specific solution would be fine.

GRedner
  • 31
  • 3
  • 5
    As Clang says, `typename Foo::Bar` is a non-deducible context. It's the same as asking the compiler to enumerate all possible arguments to `Foo` and check if any `Foo::Bar` matches the supplied `T`. Also, `template void f(typename Foo::bar){}` is the same kind of non-deducible context, and you'd have to specify `T` manually to ever call this function. (Btw, this is how partial specialization of class templates is specified.) – Xeo Dec 04 '12 at 17:48
  • Thanks, your explanation makes sense. The problem now becomes to construct a workaround. – GRedner Dec 04 '12 at 19:23

3 Answers3

1

The problem is that T is part of the name of the type Foo<T>::Bar, but it's not part of the structure of the type.

A possible solution would be to encode T in the structure of the type:

template<typename Outer, typename Inner> struct Nested: public Inner {
  using Inner::Inner;
};
template<typename T> struct Foo {
  struct BarImpl {
    int data;
  };
  using Bar = Nested<Foo<T>, BarImpl>;
};

template <typename T> struct is_bar: std::false_type {};
template <typename T, typename U> struct is_bar<Nested<Foo<T>, U>>:
  std::is_same<typename Foo<T>::Bar, Nested<Foo<T>, U>> {};

Testing:

static_assert(is_bar<Foo<int>::Bar>::value, "!");
static_assert(!is_bar<Foo<int>>::value, "!");
static_assert(!is_bar<int>::value, "!");
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • This is clever - possibly too clever. I worry that anyone doing maintenance on this code (read: future me) will have trouble unraveling it. I have an alternate solution that uses boost::tti, but my reputation is to low for me to answer my own question within 8 hours of asking it. So you'll all have to wait until tonight :) – GRedner Dec 04 '12 at 19:26
1

Here's a hideously inelegant solution to my own question, using TTI (http://svn.boost.org/svn/boost/sandbox/tti):

First, add a dummy tag to Bar:

template <typename T>
struct Foo
{
  struct Bar
  {
    typedef void i_am_bar;
    int data;
  };
};

Next, use TTI to check for that tag:

BOOST_TTI_HAS_TYPE(i_am_bar);

template <typename T>
struct is_bar : boost::tti::has_type_i_am_bar<T>
{};
...
BOOST_MPL_ASSERT(( is_bar<Foo<int>::Bar> ));
BOOST_MPL_ASSERT_NOT(( is_bar<Foo<int> > ));
BOOST_MPL_ASSERT_NOT(( is_bar<int> ));

Yucky to be sure, but it satisfies my use-case.

GRedner
  • 31
  • 3
  • You don't really need Boost.TTI for that, writing a trait that checks for the existence of that nested typedef is [relatively easy](http://stackoverflow.com/a/3980926/500104) (the `has_element_type` struct). Good job on finding a workaround, though. – Xeo Dec 05 '12 at 03:14
0

compilers are correct, the simple and easy to understand explanation is: they just don't want to substitute all possible types T just to realise is there a nested type bar inside of given template. More precise explanation you may find in a 'classic' (and well known I hope) book about templates: "C++ Templates - The Complete Guide".

fortunately C++11 helps you to do it even better! :)

#include <type_traits>

template <typename T>
struct has_nested_bar
{
    template <typename W>
    struct wrapper {};

    template <typename C>
    static std::true_type check(
        const wrapper<C>*
      , const typename C::Bar* = nullptr
      );

    template <class C>
    static std::false_type check(...);

    constexpr static bool value = std::is_same<
        decltype(check<T>(nullptr))
      , std::true_type
      >::type::value;
    typedef std::integral_constant<bool, value> type;
};

this metafucntion would check if a given type has nested Bar type (as far as I understant it was initial purpise of your is_bar).

template <typename T>
struct Foo
{
    struct Bar
    {
        int data;
    };
};

struct Bar {};

int main()
{
    std::cout << has_nested_bar<Foo<int>>::value << std::endl;
    std::cout << has_nested_bar<Bar>::value << std::endl;
    return 0;
}

will output:

zaufi@gentop /work/tests $ ./has-nested-bar
1
0

later you may combine this metafunction with your is_foo to check that nested Bar actually is inside of a Foo...

zaufi
  • 6,811
  • 26
  • 34
  • The purpose was to test whether a type *is* a nested `Bar`, not if it *has* one. – Xeo Dec 04 '12 at 20:45
  • @zaufi - thanks, but as Xeo says this isn't what I'm looking for. However, my "ugly" solution uses a utility of this type. – GRedner Dec 05 '12 at 02:39