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

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

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 скомпилирует код, но выдаст предупреждение и метафункция не сработает (всегда 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>
                   ^

Есть ли обходной путь для этой проблемы? Решение, специфичное для С++ 11, было бы хорошо.


person GRedner    schedule 04.12.2012    source источник
comment
Как говорит Clang, typename Foo<T>::Bar — невыводимый контекст. Это то же самое, что просить компилятор перечислить все возможные аргументы для Foo и проверить, соответствует ли какой-либо Foo<U>::Bar предоставленному T. Кроме того, template<class T> void f(typename Foo<T>::bar){} является таким же невыводимым контекстом, и вам придется указать T вручную, чтобы вызвать эту функцию. (Кстати, именно так указывается частичная специализация шаблонов классов.)   -  person Xeo    schedule 04.12.2012
comment
Спасибо, ваше объяснение имеет смысл. Теперь проблема заключается в том, чтобы построить обходной путь.   -  person GRedner    schedule 04.12.2012


Ответы (3)


Вот ужасно неэлегантное решение моего собственного вопроса с использованием 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> ));

Неприятно, конечно, но это удовлетворяет мой вариант использования.

person GRedner    schedule 05.12.2012
comment
На самом деле вам не нужен Boost.TTI для этого, написать трейт, который проверяет существование этого вложенного typedef, относительно легко (структура has_element_type). Однако хорошая работа по поиску обходного пути. - person Xeo; 05.12.2012

Проблема в том, что 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, "!");
person ecatmur    schedule 04.12.2012
comment
Это умно - возможно, даже слишком умно. Я беспокоюсь, что у любого, кто занимается обслуживанием этого кода (читай: будущий я), возникнут проблемы с его распутыванием. У меня есть альтернативное решение, использующее boost::tti, но моя репутация слишком низка, чтобы я мог ответить на свой вопрос в течение 8 часов после того, как его задал. Так что всем придется подождать до вечера :) - person GRedner; 04.12.2012

компиляторы правы, простое и понятное объяснение таково: они просто не хотят подставлять все возможные типы T только для того, чтобы понять, есть ли внутри заданного шаблона вложенная панель типов. Более точное объяснение вы можете найти в "классической" (и, надеюсь, хорошо известной) книге о шаблонах: "C++ Templates — The Complete Guide".

к счастью, 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...

person zaufi    schedule 04.12.2012
comment
Цель состояла в том, чтобы проверить, является ли тип вложенным Bar, а не имеет ли он его. - person Xeo; 05.12.2012
comment
@zaufi - спасибо, но, как говорит Ксео, это не то, что я ищу. Однако мое уродливое решение использует утилиту этого типа. - person GRedner; 05.12.2012