2016-03-12 2 views
3

Как я могу вызвать функцию, которая соответствует шаблонам C++? Например, если у меня есть функции а и Ь:C++ шаблоны: вызов какой-либо функции соответствует двум вариантам

void a_impl(string, int){} 
void b_impl(int, string){} 

template<typename X, typename Y> 
void a(X x, Y y){ 
    a_impl(x, y); 
} 

template<typename X, typename Y> 
void b(X x, Y y){ 
    b_impl(x, y); 
} 

template<typename X, typename Y> 
void a_or_b(X x, Y y); 

Как реализовать a_or_b таким образом, что она вызывает a(x, y), если он совпадает, иначе назвать b(x, y)?


То, что я пытаюсь сделать это for_each функция, которая может обрабатывать эти случаи:

vector<pair<string, int>> v1 = {{"one", 1}, {"two", 2}}; 

for_each(v1, [](string x, int y){ 
    cout << x << " " << y << endl; 
}); 

vector<int> v2 = {1, 2, 3}; 

for_each(v2, [](int x){ 
    cout << x << endl; 
}); 

До сих пор я это работает для обоих наборов и отдельных переменных независимо друг от друга, но я хочу, соответствующую версию для автоматического выбора. Вот моя реализация до сих пор; unpack есть apply_from_tuple с этой страницы http://www.cppsamples.com/common-tasks/apply-tuple-to-function.html.

template<typename Range, typename Func> 
void for_each_unpack(Range && range, Func && func){ 
    for (auto && element : range){ 
    using Element = decltype(element); 
    unpack(std::forward<Func>(func), std::forward<Element>(element)); 
    } 
} 

template<typename Range, typename Func> 
void for_each_nounpack(Range && range, Func && func){ 
    for (auto && element : range){ 
    using Element = decltype(element); 
    std::forward<Func>(func)(std::forward<Element>(element)); 
    } 
} 

Редактировать: Получил работу благодаря @jotik. Я поставил код на github https://github.com/csiz/for_each.

+1

Марка 'a' и' b' тем же именем. – songyuanyao

+0

Работает для конкретного примера, но не для шаблонов, подобных моему for_each. Вы получите переопределение шаблона ... for_each. (Я отредактировал пример, чтобы лучше понять мою проблему.) – csiz

+0

Вы слышали о SFINAE? Отправка тегов? Классы признаков? – Yakk

ответ

3

Используйте тип возвращаемого трейлинга с decltype и SFINAE:

#include <iostream> 
#include <string> 
#include <utility> 


void a(std::string, int) { std::cout << "a" << std::endl; } 
void b(int, std::string) { std::cout << "b" << std::endl; } 

template <typename ... Args> 
auto a_or_b(Args && ... args) 
    -> decltype(a(std::forward<Args>(args)...)) 
{ return a(std::forward<Args>(args)...); } 

template <typename ... Args> 
auto a_or_b(Args && ... args) 
    -> decltype(b(std::forward<Args>(args)...)) 
{ return b(std::forward<Args>(args)...); } 

int main() { 
    std::string s; 
    int i; 
    a_or_b(s, i); // calls a 
    a_or_b(i, s); // calls b 
} 

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

template <typename X, typename Y> 
auto a_or_b(X x, Y y) -> decltype(a(x, y)) 
{ return a(x, y); } 

template <typename X, typename Y> 
auto a_or_b(X x, Y y) -> decltype(b(x, y)) 
{ return b(x, y); } 

Как работает SFINAE в этом случае является следующим. Обратите внимание, что существует 2 определения шаблона для a_or_b. И когда вы пишете вызов функции на a_or_b, компилятор пытается выяснить, какой из a_or_b вы хотели позвонить. Из-за SFINAE он игнорирует любой шаблон a_or_b, для которого он не может определить тип. Например. для вызова a_or_b(s, i); (обратный) тип возврата decltype(b(std::forward<Args>(args)...)) для второго определения a_or_b не работает, поэтому второе определение a_or_b не рассматривается компилятором.

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

template <typename ... Args> 
decltype(b(std::forward<Args>(args)...)) a_or_b(Args && ... args) 
{ return b(std::forward<Args>(args)...); } 
+0

Спасибо! Это сработало, и я поместил код в github https://github.com/csiz/for_each – csiz

+0

@csiz Обратите внимание, что идентификаторы, начинающиеся с символа подчеркивания ('_'), зарезервированы. – jotik

+0

Технически это '__' или' _Caps', которые зарезервированы, но я удалил '_', так как они не были полезны. – csiz

1

Вот интересный вариант вашей идеи. Передайте кортеж функций и пакет аргументов. Затем вызовите все функции из этого кортежа (слева направо), которые могут принимать эти заданные аргументы. Кроме того, сохраните все возвращаемые значения (если они есть) из тех функций, которые вызывают в кортеже. Здесь я использую void_t (std::void_t в C++ 17) аспект SFINAE:

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

template <typename T> 
using void_t = void; 

template <std::size_t N, typename VoidTFailed, typename EnableIfFailed, typename Tuple, typename... Args> 
struct Try { 
    static bool execute (Tuple&&, Args&&...) { 
     std::cout << "No function found.\n"; 
     return false; 
    } 
}; 

template <std::size_t N, typename VoidTFailed, typename Tuple, typename... Args> 
struct Try<N, VoidTFailed, std::enable_if_t<(N < std::tuple_size<Tuple>::value)>, Tuple, Args...> : std::conditional_t<(N < std::tuple_size<Tuple>::value - 1), 
    Try<N+1, void, void, Tuple, Args...>, 
    Try<N+1, int, int, Tuple, Args...> // If N == std::tuple_size<Tuple>::value - 1, then simply inherit from the primary template above, as inheriting from Try<N+1, void, void, Tuple, Args...> will cause a compiling error when std::tuple_element_t<N+1, Tuple> is instantiated in the specialization below. 
> {}; 

struct NoReturnValue { 
    friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) { 
     return os << "[no value returned]"; 
    } 
}; 

template <std::size_t N, typename Tuple, typename EnableIfFailed, typename... Args> 
struct Continue { 
    static auto execute (Tuple&& functionTuple, Args&&... args) { 
     const auto singleTuple = std::make_tuple (std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...)); 
     return std::tuple_cat (singleTuple, Try<N+1, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...)); 
    } 
}; 

template <std::size_t N, typename Tuple, typename... Args> // This specialization is only used if the function in question returns void. 
struct Continue<N, Tuple, std::enable_if_t<std::is_void<decltype(std::declval<std::tuple_element_t<N, std::decay_t<Tuple>>>()(std::declval<std::decay_t<Args>>()...))>::value>, Args...> { 
    static auto execute (Tuple&& functionTuple, Args&&... args) { 
     std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...); 
     return std::tuple_cat (std::make_tuple(NoReturnValue{}), Try<N+1, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...)); 
    } 
}; 

template <std::size_t N, typename Tuple, typename EnableIfFailed, typename... Args> 
struct Stop { 
    static auto execute (Tuple&& functionTuple, Args&&... args) { 
     return std::make_tuple (std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...)); 
    } 
}; 

template <std::size_t N, typename Tuple, typename... Args> // This specialization is only used if the function in question returns void. 
struct Stop<N, Tuple, std::enable_if_t<std::is_void<decltype(std::declval<std::tuple_element_t<N, std::decay_t<Tuple>>>()(std::declval<std::decay_t<Args>>()...))>::value>, Args...> { 
    static auto execute (Tuple&& functionTuple, Args&&... args) { 
     std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...); 
     return std::make_tuple(NoReturnValue{}); 
    } 
}; 

template <std::size_t N, typename Tuple, typename... Args> 
struct Try<N, void_t<decltype(std::declval<std::tuple_element_t<N, Tuple>>()(std::declval<Args>()...))>, 
     std::enable_if_t<(N < std::tuple_size<Tuple>::value)>, Tuple, Args...> : std::conditional_t<(N < std::tuple_size<Tuple>::value - 1), 
    Continue<N, Tuple, void, Args...>, 
    Stop<N, Tuple, void, Args...> 
> {}; 

template <typename Tuple, typename... Args> 
auto executeAllPossible (Tuple&& functionTuple, Args&&... args) { 
    return Try<0, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...); 
} 

// Testing 
int foo (int, char, bool, int, int) {std::cout << "foo\n"; return 8;} 
bool bar (int, char, bool, double, long, bool) {std::cout << "bar\n"; return true;} 
void goo (int, char&&, bool, float, int) {std::cout << "goo\n";} 
double baz (int, const char, bool&&, double, std::size_t) {std::cout << "baz\n"; return 4.5;} 

template <typename Ch, typename Tr, typename Tuple, std::size_t... Is> 
void print_tuple_impl (std::basic_ostream<Ch, Tr>& os, const Tuple& t, std::index_sequence<Is...>) { 
    using A = int[]; 
    (void)A{(void(os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), 0)...}; 
} 

template <typename Ch, typename Tr, typename... Args> 
decltype(auto) operator<< (std::basic_ostream<Ch, Tr>& os, const std::tuple<Args...>& t) { 
    os << "tuple{"; 
    print_tuple_impl(os, t, std::index_sequence_for<Args...>{}); 
    return os << "}"; 
} 

int main() { 
    const auto tuple = executeAllPossible (std::make_tuple(foo, bar, goo, baz), 2, 'c', true, 3.5, 8); 
    std::cout << std::boolalpha << "output = " << tuple << '\n'; 
} 

Выход:

foo goo baz 
output = tuple{8, [no value returned], 4.5} 
Смежные вопросы