Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Take as parameters only objects for which the operator+ is defined in C++

How can I take as parameter to the function only objects (and types, like int, double or float) for which the operator+ is defined in C++?

I am almost absolutely sure that the trick should be done somehow with the help of the template, but since I am very new to C++ I can not figure it by myself.

The example of such a function declaration would be very nice.

like image 280
trafalgarLaww Avatar asked Oct 19 '17 14:10

trafalgarLaww


3 Answers

The most straightforward solution, with decltype

template<typename T, typename U>
auto func(T const& t, U const& u) -> decltype(t + u) {
    return t + u;
}

If you pass two objects which cannot be added, decltype will be ill-formed (as well as the function-template's definition).


This was accepted, so I feel obligated to introduce a substantial improvement. Let's add perfect forwarding, as the comments suggested:

#include <utility>

template<typename T, typename U>
auto func(T&& t, U&& u) -> decltype(std::forward<T>(t) + std::forward<U>(u)) {
    return std::forward<T>(t) + std::forward<U>(u);
}

Pretty straightforward as well, despite being somewhat verbose. Turn t and u into forwarding references. Then use std::forward to make sure the value categories of the references are being preserved.

like image 106
StoryTeller - Unslander Monica Avatar answered Nov 13 '22 05:11

StoryTeller - Unslander Monica


namespace details {
  template<template<class...>class, class, class...>
  struct can_apply:std::false_type{};

  // C++17 has void_t.  It is useful.  Here is a short implementation:
  template<class...>struct voider{using type=void;};
  template<class...Ts>using void_t=typename voider<Ts...>::type;

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

template<class A, class B>
using add_result = decltype( std::declval<A>()+std::declval<B>() );

template<class A, class B>
using can_add = can_apply< add_result, A, B >;

now can_add<int, int> is (inherited from) std::true_type, while can_add<std::string, void*> is (inherited from) std::false_type.

Now, if you are using a non-MSVC c++14 compiler, this works:

template<class A, class B,
  std::enable_if_t<can_add<A&,B&>{}, int> =0
>
void only_addable( A a, B b ) {
  a+b; // guaranteed to compile, if not link
}

or

template<class A, class B>
std::enable_if_t<can_add<A&,B&>{}, void> // void is return value
only_addable( A a, B b ) {
  a+b; // guaranteed to compile, if not link
}

In c++2a the concept proposal will make much of this syntax cleaner.

There is a proposal for is_detected which works like my can_apply above.

The declval part of the add_result is annoying. Short of concepts, I'm unaware of how to remove it cleanly.

like image 5
Yakk - Adam Nevraumont Avatar answered Nov 13 '22 04:11

Yakk - Adam Nevraumont


If you want to extract this to a separate detector metafunction, it could look like

template <typename T>
struct is_addable
{
private:

    template <typename A, typename B = decltype(std::declval<A>() + std::declval<A>())>
    static std::true_type check (A *);

    template <typename A>
    static std::false_type check (...);

public:

    static constexpr bool value = decltype(check<T>(nullptr))::value;
};

Then, you can sfinae-constrain your function:

template <typename A, typename = std::enable_if_t<is_addable<A>::value>>
void func (A x, A y)
{
    // ...
}

This is the C++03 way. Yakk presented a modern C++14 solution.

like image 4
lisyarus Avatar answered Nov 13 '22 04:11

lisyarus