Проблема с мета-функцией для определения класса внутри шаблона-класса

У меня есть код со следующей структурой:

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

Я хочу написать метафункции, которые скажут мне, является ли тип Foo или Bar. Первый прост:

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> ));

Тем не менее, тот же подход не работает для Бар:

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

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

Этот код отклонен компилятором. GCC говорит:

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

Как ни странно, clang скомпилирует код, но выдает предупреждение, и метафункция не работает (всегда ложно):

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>
^

Есть ли решение этой проблемы? C ++ 11-специфичное решение было бы хорошо.

2

Решение

Вот ужасно нелегкое решение моего собственного вопроса с использованием TTI (http://svn.boost.org/svn/boost/sandbox/tti):

Сначала добавьте фиктивный тег в Bar:

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

Затем используйте TTI для проверки этого тега:

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, чтобы быть уверенным, но это удовлетворяет мой вариант использования.

1

Другие решения

Проблема в том, что T является частью название типа Foo<T>::Bar, но это не часть состав типа.

Возможное решение было бы закодировать T в структуре типа:

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>> {};

Тестирование:

static_assert(is_bar<Foo<int>::Bar>::value, "!");
static_assert(!is_bar<Foo<int>>::value, "!");
static_assert(!is_bar<int>::value, "!");
1

Компиляторы верны, простое и понятное объяснение: они просто не хотят заменять все возможные типы T просто чтобы понять, есть ли вложенная строка типа внутри данного шаблона. Более точное объяснение вы можете найти в «классической» (и, я надеюсь, хорошо известной) книге о шаблонах: «Шаблоны C ++ — Полное руководство».

К счастью, C ++ 11 поможет вам сделать это еще лучше! 🙂

#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;
};

эта метафункция будет проверять, является ли данный тип вложенным Bar типа (насколько я понимаю, это была первоначальная цель вашего 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;
}

будет выводить:

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

позже вы можете объединить эту метафункцию с is_foo проверить, что вложено Bar на самом деле находится внутри Foo

0