Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specializing templates based on methods

Recently I've been programming a lot in Java, now I'm coming back to my C++ roots (I really started missing the pointers and segmentation faults). Knowing that C++ has a broad support for templates I was wondering whether it has some capabilities of Java which could be useful for writing generalized code. Lets say I'have two groups of classes. One of them has the first() method, the other one has the second() method. Is there a way of specializing the templates to be picked by the compiler depending on the methods one class possesses? I'm aiming at behavior which is similar to the one of Java:

public class Main {
    public static void main(String[] args) {
        First first = () -> System.out.println("first");
        Second second = () -> System.out.println("second");
        method(first);
        method(second);
    }

    static <T extends First> void method(T argument) {
        argument.first();   
    }

    static <T extends Second> void method(T argument) {
        argument.second();
    }
}

Where First and Second are interfaces. I know I could group both of these groups by deriving each of them from an upper class, but it's not always possible (no autoboxing in C++ and some classes don't inherit from a common ancestor).

A good example of my needs is the STL library, where some classes have methods like push() and some others have insert() or push_back(). Lets say I want to create an function which has to insert multiple values into an container using an variadic function. In Java it's easy to perform because collections have a common ancestor. In C++ on the other hand it's not always the case. I tried it by duck-typing, but the compiler yields an error message:

template <typename T>
void generic_fcn(T argument) {
    argument.first();
}

template <typename T>
void generic_fcn(T argument) {
    argument.second();
}

So my question is: Is implementing such behavior possible without creating unnecessary boileplate code by specializing every single case?

like image 853
Adrian Jałoszewski Avatar asked Aug 28 '16 17:08

Adrian Jałoszewski


People also ask

What is the specialty of a template function give example?

Template Specialization in C++ Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items.

When we specialize a function template it is called?

To do so, we can use a function template specialization (sometimes called a full or explicit function template specialization) to create a specialized version of the print() function for type double.

What is a specialized template?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.


4 Answers

Instead of <T extends First>, you will use something we call sfinae. This is a technique about adding constaints on a function based on parameter types.

Here's how you'd do it in c++:

template <typename T>
auto generic_fcn(T argument) -> void_t<decltype(argument.first())> {
    argument.first();
}

template <typename T>
auto generic_fcn(T argument) -> void_t<decltype(argument.second())> {
    argument.second();
}

For the function to exist, the compiler will need the type of argument.second() or the type of argument.first(). If the expression does not yield a type (ie. T has not a first() function), the compiler will try another overload.

void_t is implemented as follow:

template<typename...>
using void_t = void;

Another great thing is that if you have such class:

struct Bummer {
    void first() {}
    void second() {}
};

Then the compiler will effectively tell you that the call is ambiguous because the type match both constraints.


If you really want to test if a type extends another (or implement, in c++ it's the same thing) you can use the type trait std::is_base_of

template <typename T>
auto generic_fcn(T argument) -> std::enable_if_t<std::is_base_of<First, T>::value> {
    argument.first();
}

template <typename T>
auto generic_fcn(T argument) -> std::enable_if_t<std::is_base_of<Second, T>::value> {
    argument.second();
}

To read more about this topic, check sfinae on cpprefence, and you can check available traits provided by the standard library.

like image 130
Guillaume Racicot Avatar answered Oct 05 '22 01:10

Guillaume Racicot


so many options available in c++.

My preference is to favour free functions and return any result type correctly.

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

struct X
{
  int first() { return 1; }
};

struct Y
{
  double second() { return 2.2; }
};


//
// option 1 - specific overloads
//

decltype(auto) generic_function(X& x) { return x.first(); }
decltype(auto) generic_function(Y& y) { return y.second(); }

//
// option 2 - enable_if
//

namespace detail {
  template<class T> struct has_member_first
  {
    template<class U> static auto test(U*p) -> decltype(p->first(), void(), std::true_type());
    static auto test(...) -> decltype(std::false_type());
    using type = decltype(test(static_cast<T*>(nullptr)));
  };
}
template<class T> using has_member_first = typename detail::has_member_first<T>::type;

namespace detail {
  template<class T> struct has_member_second
  {
    template<class U> static auto test(U*p) -> decltype(p->second(), void(), std::true_type());
    static auto test(...) -> decltype(std::false_type());
    using type = decltype(test(static_cast<T*>(nullptr)));
  };
}
template<class T> using has_member_second = typename detail::has_member_second<T>::type;

template<class T, std::enable_if_t<has_member_first<T>::value>* =nullptr> 
decltype(auto) generic_func2(T& t)
{
  return t.first();
}

template<class T, std::enable_if_t<has_member_second<T>::value>* =nullptr> 
decltype(auto) generic_func2(T& t)
{
  return t.second();
}

//
// option 3 - SFNAE with simple decltype
//

template<class T>
auto generic_func3(T&t) -> decltype(t.first())
{
  return t.first();
}

template<class T>
auto generic_func3(T&t) -> decltype(t.second())
{
  return t.second();
}


int main()
{
  X x;
  Y y;

  std::cout << generic_function(x) << std::endl;
  std::cout << generic_function(y) << std::endl;

  std::cout << generic_func2(x) << std::endl;
  std::cout << generic_func2(y) << std::endl;

  std::cout << generic_func3(x) << std::endl;
  std::cout << generic_func3(y) << std::endl;

}
like image 44
Richard Hodges Avatar answered Oct 05 '22 01:10

Richard Hodges


You can dispatch the call as it follows:

#include<utility>
#include<iostream>

struct S {
    template<typename T>
    auto func(int) -> decltype(std::declval<T>().first(), void())
    { std::cout << "first" << std::endl; }

    template<typename T>
    auto func(char) -> decltype(std::declval<T>().second(), void())
    { std::cout << "second" << std::endl; }

    template<typename T>
    auto func() { return func<T>(0); }
};

struct First {
    void first() {}
};

struct Second {
    void second() {}
};

int main() {
    S s;
    s.func<First>();
    s.func<Second>();
}

Method first is preferred over second if a class has both of them.
Otherwise, func uses function overloading to test the two methods and choose the right one.
This technique is called sfinae, use this name to search on the web for further details.

like image 33
skypjack Avatar answered Oct 05 '22 02:10

skypjack


Here is a little library that helps you determine if a member exists.

namespace details {
  template<template<class...>class Z, class always_void, class...>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;

Now we can write has first and has second easily:

template<class T>
using first_result = decltype(std::declval<T>().first());
template<class T>
using has_first = can_apply<first_result, T>;

and similarly for second.

Now we have our method. We want to call either first or second.

template<class T>
void method_second( T& t, std::true_type has_second ) {
  t.second();
}
template<class T>
void method_first( T& t, std::false_type has_first ) = delete; // error message
template<class T>
void method_first( T& t, std::true_type has_first ) {
  t.first();
}
template<class T>
void method_first( T& t, std::false_type has_first ) {
  method_second( t, has_second<T&>{} );
}
template<class T>
void method( T& t ) {
  method_first( t, has_first<T&>{} );
}

this is known as tag dispatching.

method calls the method_first which is determined if T& can be invoked with .first(). If it can be, it calls the one that calls .first().

If it cannot, it calls the one that forwards to method_second and tests if it has .second().

If it has neither, it calls an =delete function, which generates an error message at compile time.

There are many, many, many ways to do this. I personally like tag dispatching because you can get better error messages out of failure to match than SFIANE generates.

In C++17 you can be more direct:

template<class T>
void method(T & t) {
  if constexpr (has_first<T&>{}) {
    t.first();
  }
  if constexpr (has_second<T&>{}) {
    t.second();
  }
}
like image 43
Yakk - Adam Nevraumont Avatar answered Oct 05 '22 03:10

Yakk - Adam Nevraumont