Напишите метафункцию, возвращающую тип из списка типов, который имеет typedef данного типа в C++11.

Я столкнулся со сценарием, в котором у меня есть список классов в списке вариативных шаблонов, и, учитывая тип (Target_), я хочу найти класс в списке (ContainingClass_), который typedefs Target_ как ContainingClass_::Class.

Вот моя текущая реализация грубой силы:

#include <iostream>
#include <cstdlib>
#include <type_traits>
#include <tuple>

template <uint32_t ID_, class Class_>
struct ContainingClass
{
    using Class = Class_;
    static constexpr uint32_t id() { return ID_; }
};

// Get the index of a type in a type list, or -1 on error (So that we only get the important static_assert).
template <typename Target_, typename ...List_>
struct IndexOf : std::integral_constant<int, -1> {};

template <typename Target_, typename NotTarget_, typename ...List_>
struct IndexOf<Target_, NotTarget_, List_...> : std::integral_constant<std::size_t, 1 + IndexOf<Target_, List_...>::value> {};

template <typename Target_, typename ...List_>
struct IndexOf<Target_, Target_, List_...> : std::integral_constant<std::size_t, 0> {};

// Check if a type exists in a typelist.
template <typename Target_, typename... List_>
struct TypeExists;

template <typename Target_>
struct TypeExists<Target_> : std::false_type {};

template <typename Target_, typename... List_>
struct TypeExists<Target_, Target_, List_...> : std::true_type {};

template <typename Target_, typename NotTarget_, typename... List_>
struct TypeExists<Target_, NotTarget_, List_...> : TypeExists<Target_, List_...> {};

// **THE META-FUNCTION THAT THE QUESTION IS ABOUT**
// Find the ContaingClass that typedefs Target_ as "Class" inside of it.
template <class Target_, class ...ContainingClasses_>
struct ContainingClassFinder
{
    static_assert(TypeExists<Target_, typename ContainingClasses_::Class...>::value, "No ContainingClass found for Target_.");
    using ContainingClass = typename std::tuple_element<IndexOf<Target_, typename ContainingClasses_::Class...>::value, 
                                                        std::tuple<ContainingClasses_...>>::type;
};

using namespace std;

// Use the meta function to return the id of the ContainingClass that contains a type.
template <class Target_, class ...ContainingClasses_>
uint32_t get_containing_id(ContainingClasses_...)
{
    return ContainingClassFinder<Target_, ContainingClasses_...>::ContainingClass::id();
}

struct Foo {};
struct Bar {};
struct Test {};
struct NonExistent {};

int main()
{
    // Prove that the right class was found be printing its ID out.
    // Prints 2.
    cout << get_containing_id<Test>(ContainingClass<0, Foo>{}, ContainingClass<1, Bar>{}, ContainingClass<2, Test>{}) << endl;
    // Causes static_assert.
    //cout << get_containing_id<NonExistent>(ContainingClass<0, Foo>{}, ContainingClass<1, Bar>{}, ContainingClass<2, Test>{}) << endl;
    return EXIT_SUCCESS;
}

Проблема в том, что он зависит от std::tuple и выполняет два линейных поиска: один для проверки существования (используя первую вспомогательную метафункцию) и один для получения типа (используя std::tuple_element).

В идеале я хотел бы получить все это за один раз, без необходимости использования двух вспомогательных метафункций и std::tuple; это практично? Если нет, любые улучшения в моей реализации будут оценены.

ПРИМЕЧАНИЕ:

<сильный>1. Эта метафункция будет использоваться как деталь реализации библиотеки; Ускорение не вариант.

<сильный>2. Результатом метафункции должен быть тип, в частности тип содержащего его типа, в котором был найден целевой тип. Получение id() класса — это простой способ показать, что метафункция работает.

<сильный>3. Под метафункцией я имею в виду шаблонную структуру, подобную той, что определена в <type_traits>.


person rationalcoder    schedule 25.09.2016    source источник
comment
Вместо того, чтобы заново изобретать, может быть, взглянуть на Boost.Hana?   -  person Kerrek SB    schedule 25.09.2016
comment
@KerrekSB Спасибо, что напомнили мне; Я забыл упомянуть, что это часть библиотеки, и повышение просто не вариант.   -  person rationalcoder    schedule 25.09.2016
comment
Смотрите мой обновленный ответ: компилируется и работает по назначению (по крайней мере, мной)   -  person Adrian Colomitchi    schedule 25.09.2016


Ответы (2)


Как насчет следующего решения?

Нет std::tuple и, если не ошибаюсь, только один линейный поиск.

#include <iostream>
#include <cstdlib>
#include <type_traits>

template <uint32_t, class Class_>
struct ContainingClass
 { using Class = Class_; };

struct Foo {};
struct Bar {};
struct Test {};

template <typename, typename ...>
struct getContType;

template <typename T>
struct getContType<T>
 { using type = void; };

template <typename T, typename CC0, typename ... CCs>
struct getContType<T, CC0, CCs...>
 { using type = typename std::conditional<std::is_same<T,
         typename CC0::Class>::value, CC0,
         typename getContType<T, CCs...>::type>::type; };

int main()
 {
   static_assert(std::is_same<ContainingClass<2, Test>,
                 getContType<Test, ContainingClass<0, Foo>,
                 ContainingClass<1, Bar>, ContainingClass<2, Test>>::type
                 >::value, "!");

   return EXIT_SUCCESS;
 }
person max66    schedule 25.09.2016
comment
Это выглядит многообещающе, но получение идентификатора нужно было просто для того, чтобы показать, что был найден правильный тип. Мета-функция должна иметь результат типа, в частности, того содержащего типа, в котором был найден целевой тип. - person rationalcoder; 25.09.2016
comment
@rationalcoder - понятно... ну... функция может возвращать объект определенного типа; не тип; Я изменил свое решение, чтобы оно возвращало объект типа ContainingClass<2, Test>; если вас интересует только тип, я полагаю, вам следует создать структуру с типом, а не с функцией. - person max66; 25.09.2016
comment
Ааа, тут дело в формулировках. Под метафункцией я подразумеваю шаблонную структуру, как и те, которые определены в type_traits. Я обновлю свой вопрос, чтобы прояснить это. Я имею в виду это в том же смысле, что и этот парень: youtube.com/watch?v=Am2is2QCvxY - person rationalcoder; 25.09.2016
comment
Это там сейчас. Он есть в примечании внизу. Если вы посмотрите комментарии в коде, я пометил ContainingClassFinder как рассматриваемую метафункцию. - person rationalcoder; 25.09.2016
comment
@rationalcoder - не уверен, что понял, но... добавил struct getContType; это то, что вы ищете? Добавил второй static_assert() в main() для проверки. - person max66; 25.09.2016
comment
На самом деле да. Решение, которое я имел в виду, было бы просто вашей структурой getContType (мета-функцией). Только эти три структуры внизу — это все, что я искал. Если вы сведете его только к этим трем, это будет принятый ответ. Это тоже довольно элегантно; Я не смог сделать то же самое, прежде чем придумал оригинальное решение. Теперь я вижу, что я сделал неправильно. Благодарю вас! - person rationalcoder; 25.09.2016
comment
@rationalcoder - добро пожаловать; упрощенное решение - person max66; 25.09.2016

[Обновление] Это работает для меня:

// marker for not-found types
struct none {
};

// fwd declaration
template <typename Target_,typename ...List_> struct scanner;

template <
  typename Target_,
  typename Head_, typename ...List_
>
struct scanner<Target_, Head_, List_...> {

  using found_type=typename std::conditional<
   std::is_same<Target_, Head_>::value, // or any other predicate
   Head_,
   // if not, search further
   typename scanner<Target_, List_...>::found_type
  >::type;
  // were are going to reset it anyway if in the end the found_type==none
  static constexpr int value=
     std::is_same<Target_, Head_>::value
   ? 0
   : scanner<Target_, List_...>::value+1
  ;
};

// need a recursion stopper for the scanner here.
template <typename Target_>
struct scanner<Target_> {
  using found_type=none;
  // were are going to reset it anyway if in the end the found_type==none
  static constexpr int value=-1;

};

template <typename Target, typename... List>
struct ContainingClassFinder {
private:
  using result=scanner<Target, List...>;
public:
  static constexpr int value=
      std::is_same<typename result::found_type, none>::value
    ? -1
    : result::value
  ;
  using found_type=typename std::enable_if<
     false==std::is_same<typename result::found_type, none>::value,
     typename result::found_type
  >::type;
};

/// brief test
struct dummy {

};

using good=ContainingClassFinder<
  int,
  long, dummy, int, char
>;
static_assert(std::is_same<int, good::found_type>::value, "Oops - good type failing");
static_assert(good::value==2, "Oops - good index failing");


using bad_notfound=ContainingClassFinder<
  int,
  long, dummy, float, char
>;
static_assert(bad_notfound::value==-1, "Ooops - bad type failing index");

// this should create a compilation error due to std::enable_if with a false condition
using non_existing=typename bad_notfound::found_type; // yeap, it does cause compilation error
person Adrian Colomitchi    schedule 25.09.2016
comment
Это похоже на мою первоначальную попытку, в которой я потерпел неудачу, ха-ха. Я возьму его на минутку и посмотрю, соответствует ли он моим потребностям. - person rationalcoder; 25.09.2016
comment
@rationalcoder Обновлен/обновлен до чего-то, что работает (все еще в качестве примера, не могу оценить пригодность для ваших целей) - person Adrian Colomitchi; 25.09.2016