2017-02-19 24 views
2

Мне нужно найти способ рекурсивного построения класса с учетом набора аргументов шаблона, чтобы класс наследовал от себя и построил метод f для текущего аргумента первого шаблона в списке аргументов шаблона, а затем наследует от себя, передавая остальную часть списка.рекурсивное наследование для перегрузки метода для набора аргументов шаблона

Таким образом, в основном я хочу достигнуть следующий интерфейс для класса C:

C<T1, T2, T3> c; 

c теперь есть методы C::f(T1), C::f(T2) и C::f(T3)

Мой подход до сих пор было так:

// primary template 
template <class H, class...> 
class C {}; 

// base case where class... is empty 
template <class H, class...> 
class C<H> 
{ 
public: 
    void f(const H& h){ 
     // std::cout << typeid(h).name() << "\n"; 
    } 
}; 

// recursive case where T is nonempty 
template <class H, class... T> 
class C : public C<T...> 
{ 
public: 
    void f(const H& h){ 
     // std::cout << typeid(h).name() << "\n"; 
    } 
}; 

Это фактически не компилируется, так как я получаю

error: redefinition of 'C' class C : public C

Является ли мой подход принципиально возможным и просто вопросом семантически или синтаксически недействительного кода или этот подход не работает в принципе?

ответ

1

Для начала класс не может наследовать сам.

Во-вторых, все, что вы, по-видимому, пытаетесь выполнить, состоит в том, чтобы каждый параметр шаблона генерировал метод класса, который принимает этот класс в качестве параметра.

В этом случае что-то подобное должно работать.

template<typename ...> class C; 

template<> 
class C<> {}; 

template<typename T, typename ...Args> 
class C<T, Args...> : public C<Args...> { 

public: 

    void f (const T &) 
    { 
     // Whatever... 
    } 
}; 

Обратите внимание, что это не класс, наследующий от себя. Это экземпляр шаблона, который наследуется от другого экземпляра шаблона. Каждый экземпляр шаблона является уникальным классом.

Обратите внимание, что у вас есть одно определение рассматриваемого метода класса, а не два, как вы пытались это сделать. Это небольшое улучшение.

Еще одно усовершенствование было бы рассмотреть вопрос о перестановке иерархии классов таким образом, если это возможно, чтобы сделать это, принимая во внимание другие ваши требования класса:

template<typename T> class F { 
public: 

    void f (const T &) 
    { 
    } 
}; 


template<typename ...> class C; 

template<> 
class C<> {}; 

template<typename T, typename ...Args> 
class C<T, Args...> : public C<Args...> , public F<T> { 

}; 

При таком подходе, используют ли вы C<int, float> или C<int, char *> , метод класса всегда будет объявлен как метод F<int>. Это немного сократилось от результирующего кодового поплавка, поскольку любой экземпляр C, который включает в себя int, например, будет генерировать экземпляр метода одного класса вместо двух отдельных методов, таких как C<int, float>::f(const int &) и C<int, char *>::f(const int &), что в противном случае было бы полностью идентичным.

+0

спасибо, но с вашей второй конструкцией я получаю сообщение об ошибке: 'error: member 'f' найдено в нескольких базовых классах разных типов. –

+1

Можете работать с ним, добавляя' using F :: f; ' и 'используя C :: f;' в основной шаблон. –

+0

Хорошо, попробуем это, но я заметил что-то еще: с вашим первым проектом для следующей программы 'int main() { C c; int i = 10; char ch = 'a'; c.f (i); c.f (ch); } 'Я получаю вывод' c c' ..., что означает, что вызываются только перегрузки для первого аргумента шаблона. Обратите внимание, что у меня есть 'std :: cout << typeid (T) .name() <<" \ n ";' in 'C :: f' –

1

Как альтернативный подход, я предлагаю решение, основанное на mixins. Не стесняйтесь игнорировать шаблон, введенный классом type_name, цель которого состоит в том, чтобы показать вам, что правая часть поднята на основе аргумента.

вытекает минимальный, рабочий пример:

#include<type_traits> 
#include<utility> 
#include<iostream> 

template<typename> struct type_name; 
template<> struct type_name<int> { static const char *name; }; 
template<> struct type_name<double> { static const char *name; }; 
template<> struct type_name<char> { static const char *name; }; 

const char * type_name<int>::name = "int"; 
const char * type_name<double>::name = "double"; 
const char * type_name<char>::name = "char"; 

template<typename T> 
struct Part { 
    void func(T t) { 
     std::cout << type_name<T>::name << ": " << t << std::endl; 
    } 
}; 

template<typename... T> 
struct S: private Part<T>... { 
    template<typename... Args> 
    void f(Args&&... args) { 
     using acc_t = int[]; 
     acc_t acc = { 0, (Part<std::decay_t<Args>>::func(std::forward<Args>(args)), 0)... }; 
     (void)acc; 
    } 
}; 

int main() { 
    S<int, double, char> s; 
    s.f(42); 
    s.f(0.1); 
    s.f('c'); 
    s.f('a', 0.3, 23); 
} 

Некоторые плюс этого метода:

  • Part<T> определяется только один раз для любого T, независимо от того, сколько раз вы использовать его в различные пакеты.

  • S<T, T> отклоняется во время компиляции и более, в общем, все те пакеты, которые содержат один и тот же тип два или более раз. В противном случае они родили бы несколько определений f(T), и последующий вызов был бы, вероятно, неоднозначным.

  • Вы можете вызвать f с одним параметром в соответствии с запросом. Во всяком случае, как показано в примере, вы можете вызывать f с параметрами N, а вызов эквивалентен N вызовам f с одним параметром.
    Другими словами, вы можете использовать это:

    s.f('a', 0.3, 23); 
    

    Или это:

    s.f('a'); 
    s.f(0.3); 
    s.f(23); 
    

    Результат будет одинаковым в обоих случаях.
    Эта функция может быть легко отключена в случае необходимости, определив S как это следующим образом:

    template<typename... T> 
    struct S: private Part<T>... { 
        template<typename U> 
        void f(U &&u) { 
         Part<std::decay_t<U>>::func(std::forward<U>(u)); 
        } 
    }; 
    

Смотреть это работает на wandbox.


Как примечание стороны, это обычный трюк используется для эмуляции кратные выражений в C++ 14:

template<typename... Args> 
void f(Args&&... args) { 
    using acc_t = int[]; 
    acc_t acc = { 0, (Part<std::decay_t<Args>>::func(std::forward<Args>(args)), 0)... }; 
    (void)acc; 
} 

Вы можете узнать больше о том, что на SO, а также на веб-сайте.