2015-06-26 2 views
0

Может ли кто-нибудь сказать мне, как удалить повторяющиеся специализации ниже?Специализации для разных типов

#include <iostream> 
#include <fstream> 
#include <string> 

struct Thing { 
    int a, b; 
    void load (std::istream& is) {is >> std::skipws >> a >> b;} 
}; 

struct Object { 
    int a, b, c; 
    void load (std::istream& is) {is >> std::skipws >> a >> b >> c;} 
}; 

template <typename...> struct PassArgs; 

// General case. 
template <typename First, typename... Rest> 
struct PassArgs<First, Rest...> : PassArgs<Rest...> { 
    void operator()(std::istream& is, First& first, Rest&... rest) const { 
     is >> first; 
     PassArgs<Rest...>::operator()(is, rest...); 
    } 
}; 

// Specialization for std::string needed. 
template <typename... Rest> 
struct PassArgs<std::string, Rest...> : PassArgs<Rest...> { 
    void operator()(std::istream& is, std::string& first, Rest&... rest) const { 
     while (std::getline (is, first) && first.empty()); 
     PassArgs<Rest...>::operator()(is, rest...); 
    } 
}; 

// Specialization for class Thing. 
template <typename... Rest> 
struct PassArgs<Thing, Rest...> : PassArgs<Rest...> { 
    void operator()(std::istream& is, Thing& first, Rest&... rest) const { 
     first.load(is); 
     PassArgs<Rest...>::operator()(is, rest...); 
    } 
}; 

// Specialization for class Object, but is the exact same as that for Thing. 
template <typename... Rest> 
struct PassArgs<Object, Rest...> : PassArgs<Rest...> { 
    void operator()(std::istream& is, Object& first, Rest&... rest) const { 
     first.load(is); 
     PassArgs<Rest...>::operator()(is, rest...); 
    } 
}; 


template <> 
struct PassArgs<> { 
    void operator()(std::istream&) const {} // End of recursion. 
}; 


int main() {} 

Все работает правильно, но есть способ избежать специализаций для всех классов, которые имеют функцию load(std::istream&) (и есть много в моей программе). В настоящее время у меня есть специализации для Thing, Object и многих других классов, которые имеют одинаковые строки в своих специализациях.

Кстати, это, как клиент использует PassArgs:

template <typename T, typename... Args> 
T* create (std::istream& is, Args&... args) { 
    PassArgs<Args...>()(is, args...); 
    T* t = new T(args...); 
    // Do whatever with t; 
    return t; 
} 

ответ

4

Define черты для обнаружения load члена:

template<typename T> using void_t = void; 

template<typename T, typename = void_t<>> 
    struct has_load 
    : std::false_type { }; 

template<typename T> 
    struct has_load<T, void_t<decltype(std::declval<T&>().load(std::declval<std::istream&>()))>> 
    : std::true_type 
    { }; 

Поднять фактическую нагрузку для одного типа в отдельный шаблон класса, специализированный для типов с элементом load (с использованием признака):

template<typename T, bool use_load = has_load<T>::value> 
    struct PassArg 
    { 
    static void pass(std::istream& is, T& t) { is >> t; } 
    }; 

template<typename T> 
    struct PassArg<T, true> 
    { 
    static void pass(std::istream& is, T& t) { t.load(is); } 
    }; 

Затем используйте, что в главном шаблоне:

// General case. 
template <typename First, typename... Rest> 
    struct PassArgs : PassArgs<Rest...> { 
    void operator()(std::istream& is, First& first, Rest&... rest) const 
    { 
     PassArg<First>::pass(is, first); 
     PassArgs<Rest...>::operator()(is, rest...); 
    } 
}; 

string дело может быть сделано, специализируясь PassArg тоже.

template<> 
    struct PassArg<std::string, false> 
    { 
    static void pass(std::istream& is, std::string& s) 
    { getline(is, s); } 
    }; 

N.B. Я сделал новый класс имеет статическую функцию pass не operator(), потому что если вы оказываетесь пишу это:

PassArgs<Args...>()(is, args...); 

или хуже, называя operator() по имени, как это:

PassArgs<Rest...>::operator()(is, rest...); 

Тогда вы, вероятно, дон Не хочу функтора. PassArgs не имеет гражданства, поэтому нет смысла создавать его экземпляр, и если вы должны явно указать operator(), тогда вы делаете это неправильно. Дайте функции правильное имя и вызовите это и сделайте его статическим:

PassArgs<Rest...>::sensible_name(is, rest...); 
+0

Если вы используете функцию загрузки вместо типа загрузки, вы можете получить настройку ADL в качестве бонуса. – Yakk

+0

@Yakk, да, хотя это не всегда желательно –

+0

Я думаю, что здесь есть что-то не так с классом 'void_t'. Я попытался полностью протестировать вашу реализацию: http://ideone.com/pxYwUF Заменить мои оригинальные 'ForwardArgs () (is, args ...);' с 'PassArgs () (is, args ...); 'в функции' T * create (std :: istream & is, Args & ... args) 'в моей тестовой программе, и она не компилируется. – prestokeys

6

Есть примерно один bazillion способов сделать это. Вот один из них.

Во-первых, признак того, есть ли элемент load(). Вот один из способов его написания:

namespace details {  
    template<class T> 
    auto has_load_impl(int) 
     -> decltype((void)std::declval<T&>().load(std::declval<std::istream&>()), 
        std::true_type()); 

    template<class T> 
    std::false_type has_load_impl(...); 

    template<class T> 
    using has_load = decltype(has_load_impl<T>(0)); 
} 

Существует множество других способов написания этой черты. Ответ Джонатана Вакели использует, например, Walter Brown's void_t. Или вы можете использовать std::experimental::is_detected.

Далее следует написать функцию, которая загружает один аргумент, отправляет на основе результата has_load. Вот один из способов сделать это:

namespace details { 
    template<class T> 
    void do_load(std::istream& is, T& t, std::true_type /*has_load*/){ 
     t.load(is); 
    } 

    template<class T> 
    void do_load(std::istream& is, T& t, std::false_type /*has_load*/){ 
     is >> t; 
    } 
} 

template<class T> 
void load(std::istream& is, T& t){ 
    details::do_load(is, t, details::has_load<T>()); 
} 

Это также можно обойтись без отдельной черты, и SFINAE непосредственно в do_load функций, если вам не нужна черта в другом месте:

namespace details { 
    template<class T> 
    auto do_load(std::istream& is, T& t, int) -> decltype((void)t.load(is)){ 
     t.load(is); 
    } 

    template<class T> 
    void do_load(std::istream& is, T& t, ...){ 
     is >> t; 
    } 
} 

template<class T> 
void load(std::istream& is, T& t){ 
    details::do_load(is, t, 0); 
} 

Добавить перегрузки по мере необходимости для типов, требующих специальной обработки.

void load(std::istream& is, std::string& s){ 
    while (std::getline (is, s) && s.empty()); 
} 

Наконец, PassArgs само по себе может быть упрощена функции на две строки, используя знакомый трюк расширения пакета внутри рамно-Init-лист:

template<class... Args> 
void PassArgs(std::istream& is, Args&... args){ 
    using expander = int[]; 
    (void)expander{0, (load(is, args), void(), 0)... }; 
} 

Demo.

В вышеуказанном случае пользователь может настроить load с использованием ADL.В качестве альтернативы, вы можете сделать load функцию члена шаблона класса loader<T> вместо этого, и пользователей могут специализироваться loader по желанию, чтобы настроить нагрузку:

template<class T> 
struct loader { 
    static void load(std::istream& is, T& t) { 
     details::do_load(is, t, details::has_load<T>()); 
    } 
}; 

template<> struct loader<std::string>{ 
    static void load(std::istream& is, std::string& s){ 
     while (std::getline (is, s) && s.empty()); 
    } 
}; 

template<class... Args> 
void PassArgs(std::istream& is, Args&... args){ 
    using expander = int[]; 
    (void)expander{0, (loader<T>::load(is, args), void(), 0)... }; 
} 
4

Я собираюсь пройти весь путь до решения ADL.

Во-первых, здесь friend поддержка нагрузки.

struct Thing { 
    int a, b; 
    friend void load (std::istream& is, Thing& t) {is >> std::skipws >> t.a >> t.b;} 
}; 

член нагрузки на основе:

struct Object { 
    int a, b, c; 
    void load (std::istream& is) {is >> std::skipws >> a >> b >> c;} 
}; 

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

namespace meta { 
    namespace details { 
    template<template<class...>class Z, class=void, class...Ts> 
    struct can_apply : std::false_type {}; 
    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z, decltype((void)(std::declval<Z<Ts...>>())), Ts...>: 
     std::true_type 
    {}; 
    } 
    template<template<class...>class Z, class...Ts> 
    using can_apply = details::can_apply<Z,void,Ts...>; 
} 
template<class T> 
using member_load = decltype(std::declval<T>().load(std::declval<std::istream&>())); 

template<class T> 
using stream_load = decltype(std::declval<std::istream&>() >> std::declval<T>()); 

, который ведет к этому пуанта:

template<class T> 
using has_member_load = meta::can_apply< member_load, T >; 
template<class T> 
using has_stream_load = meta::can_apply< stream_load, T >; 

Теперь мы создаем loading пространство имен:

namespace loading { 
    void load(std::istream&is, std::string& s) { 
    while (std::getline (is, s) && s.empty()); 
    } 
    template<class T> 
    std::enable_if_t<has_member_load<T&>::value> 
    load(std::istream&is, T& t) { 
    t.load(is); 
    } 
    // uses ... to keep lowest priority: 
    template<class T> 
    std::enable_if_t<has_stream_load<T&>::value> 
    load(std::istream& is, T& t, ...) { 
    is >> t; 
    } 

    template<class...Ts> 
    void load_many(std::istream&is, Ts&...ts) { 
    using discard=int[]; 
    (void)discard{0,((
     load(is, ts) 
    ),void(),0)...}; 
    } 
} 

Теперь мы можем позвонить loading::load_many(is, a, b, c, d).

std::string имеет специальную функцию loading::load, а также любые другие конкретные типы в std, которые вы хотите поддержать. Например, вы можете написать template<class T, class A> void load(std::istream& is, std::vector<T,A>&) в namespace loading, и он просто сработает.

Любой класс X со свободной функцией load(istream&, X&), определенный в своем пространстве имен, будет иметь эту функцию.

В противном случае любой класс с методом .load(istream&) будет иметь этот метод.

Любой класс X с перегрузкой istream& >> X& получит это, если все выше не выполнено.

live example.

Смежные вопросы