2016-05-05 5 views
1

Я хотел бы быть в состоянии сделать что-то вроде следующего:C++ условное построение шаблона типа

struct A {}; 

template<typename T, typename U> struct B : public A {}; 

std::unique_ptr<A> chooseB(int i, int j) 
{ 
    // return B<T, U> where T and U vary with i and j (0 = int, 1 = double etc.) 
} 

i и j не известны во время компиляции. Потенциально список типов может быть достаточно длинным, что просто формирование большого условного числа, которое перечисляет все возможные пары i и j, может быть болезненным.

Есть ли хороший способ написать функцию chooseB? Возможно, я хочу обобщить это на более чем два параметра шаблона.

+1

Являются ли параметры 'selectB' известными во время компиляции? –

+0

Нет. Например, мне может понадобиться построить B на основе анализируемой информации. (Я отредактировал вопрос, чтобы сделать это ясно). – dexterurbane

+0

Похоже, вы ищете [зависимые типы] (https://en.wikipedia.org/wiki/Dependent_type), которые C++ не поддерживает. Хотя могут быть обходные пути, как это было предложено в ответе. – Elazar

ответ

4

Время для некоторых злоупотреблений.

Мы собираемся написать функцию, которая будет определять, какой тип соответствует данному индексу. Но мы не можем вернуть этот тип - тип возврата должен быть известен во время компиляции. Поэтому вместо этого мы перешлем это на функцию, которую мы предоставляем. Начну с конечным результатом:

std::unique_ptr<A> chooseB(int i, int j) 
{ 
    using choices = typelist<int, double>; 

    return pick_elem<std::unique_ptr<A>>(i, choices{}, 
     [=](auto tag1){ 
      return pick_elem<std::unique_ptr<A>>(j, choices{}, 
       [=](auto tag2) { 
        using T1 = typename decltype(tag1)::type; 
        using T2 = typename decltype(tag2)::type; 

        return std::make_unique<B<T1, T2>>(); 
       }); 
      }); 
} 

pick_elem принимает три аргумента: индекс, список выбора, и функции для пересылки вместе с. Итак, мы называем это первым индексом и вызываем лямбда - который по очереди вызывает его со вторым индексом и вызывает другую лямбду, что, наконец, делает наш B.

Вы должны явно указать тип возврата pick_elem<>, потому что ему необходимо как-то остановить рекурсию, и этот конечный шаг должен будет знать, что нужно вернуть. Итак, мы начинаем с некоторыми удобными помощниками метапрограммирования:

template <class... > struct typelist {}; 
template <class T> struct tag { using type = T; }; 

А потом pick_elem является:

template <class R, class F> 
R pick_elem(int , typelist<>, F) 
{ 
    throw std::runtime_error("wtf"); 
} 


template <class R, class T, class... Ts, class F> 
R pick_elem(int i, typelist<T, Ts...>, F f) 
{ 
    if (i == 0) { 
     return f(tag<T>{}); 
    } 
    else { 
     return pick_elem<R>(i-1, typelist<Ts...>{}, f); 
    } 
} 

Это может быть обобщена на сколь угодно много типов, делая chooseB сам VARIADIC. В некотором смысле, это на самом деле чище, чем у меня раньше. chooseB() теперь дополнительно принимает некоторые не выводимые аргументы, которые типы мы выяснили до сих пор:

template <class... Ts> 
std::unique_ptr<A> chooseB() 
{ 
    return std::make_unique<B<Ts...>>(); 
} 

template <class... Ts, class Idx, class... Rest> 
std::unique_ptr<A> chooseB(Idx i, Rest... rest) 
{ 
    using choices = typelist<int, double>; 

    return pick_elem<std::unique_ptr<A>>(i, choices{}, 
     [=](auto tag){ 
      return chooseB<Ts..., typename decltype(tag)::type>(rest...); 
     }); 
} 

Вы можете добавить некоторую безопасность вокруг этого, утверждая, что число аргументов является правильным.

+0

Ничего себе, это было быстро и впечатляюще. Я ожидал, что ответ будет примерно таким (вариативные шаблоны с рекурсией и трюками). Я все еще работаю над этим, но поскольку я использую C++ 11, а не 14, у меня не может быть общих лямбда, так что вам нужно будет сделать что-то немного отличное от лямбды в selectB. – dexterurbane

0

Вы можете сопоставить оба номера для конкретизации базового класса:

#define FWD(x) std::forward<decltype(x)>((x)) 
auto p=[](auto&&l,auto&&r){return std::make_pair(FWD(l),FWD(r));}; 

std::unique_ptr<A> chooseB(int i, int j) { 
    std::map<std::pair<int,int>,std::unique_ptr<A>> m; 
    m.emplace(p(p(0,0),std::make_unique<B<int,int>>())); 
    m.emplace(p(p(0,1),std::make_unique<B<int,double>>())); 
    m.emplace(p(p(1,0),std::make_unique<B<double,int>>())); 
    m.emplace(p(p(1,1),std::make_unique<B<double,double>>())); 
    /* and so on for different (i,j) */ 
    return std::move(m[p(i,j)]); 
} 

Если вы хотите, чтобы обобщить это более двух параметров, то есть функция взять параметр блока (Args&&...) возможно SFINAE для всех целых чисел, а вместо пары - std::tuple<std::decay_t<Args...>>.

+0

Спасибо за это. Я действительно хотел найти универсальное решение, в котором явно не учитываются все возможные комбинации «i» и «j» один за другим, но, с другой стороны, может оказаться, что что-то в соответствии с предложенными вами линиями может оказаться будучи самым прагматичным решением для того, что я хочу сделать. – dexterurbane

+0

Мое прагматическое решение, вдохновленное вашим, приведено ниже. На самом деле это, вероятно, будет работать для ОК для проблемы, но я * был заинтересован в более общих ответах, которые не зависят от конкретного перечисления всех возможностей. – dexterurbane

0

Мое прагматическое решение, вдохновленное 0x499602D2 выше. На самом деле это, вероятно, будет работать для ОК для этой проблемы - но я ответил, что был заинтересован в более общих ответах, которые не зависят от конкретного перечисления всех возможностей.

struct A 
{ 
    virtual ~A() = default; 
    virtual void printTypes() = 0; 
}; 

template<typename T, typename U> 
struct B : public A 
{ 
    virtual void printTypes() override 
    { 
     std::cout << typeid(T).name() << ", " << typeid(U).name() << std::endl; 
    } 
}; 

int main() 
{ 
    std::map<std::pair<int, int>, std::function<std::unique_ptr<A>()>> m = 
     { 
      {{0, 0}, [](){return std::unique_ptr<A>(new B<int, int>);}}, 
      {{0, 1}, [](){return std::unique_ptr<A>(new B<int, double>);}}, 
      {{1, 0}, [](){return std::unique_ptr<A>(new B<double, int>);}}, 
      {{1, 1}, [](){return std::unique_ptr<A>(new B<double, double>);}} 
     }; 

    for (int i = 0; i < 2; ++i) 
    { 
     for (int j = 0; j < 2; ++j) 
     { 
      auto a = m[std::make_pair(i, j)](); 
      a->printTypes(); 
     } 
    } 
} 
0

Сделать таблицу скачан и отправить.

template<size_t I, class... Ts> 
std::unique_ptr<A> do_make() { 
    using T = std::tuple_element_t<I/sizeof...(Ts), std::tuple<Ts...>>; 
    using U = std::tuple_element_t<I % sizeof...(Ts), std::tuple<Ts...>>; 
    return std::make_unique<B<T,U>>(); 
} 
template<class... Ts, size_t...Is> 
std::unique_ptr<A> make(size_t i, size_t j, std::index_sequence<Is...>) { 
    using fptr_t = std::unique_ptr<A> (*)(); 
    static constexpr fptr_t table[] = { do_make<Is, Ts...> ...}; 
    return table[ i * sizeof...(Ts) + j](); 
} 

template<class... Ts> 
std::unique_ptr<A> make(size_t i, size_t j) { 
    return make<Ts...>(i, j, std::make_index_sequence<sizeof...(Ts) * sizeof...(Ts)>()); 
} 
Смежные вопросы