2

I want to make a template function that takes a function and a vector and uses the function to map that vector to another vector that will be returned by the function template.

If the function taken as an argument is a free function, it may have one of two signatures.

// T is the parameter of the function template
T sig1(const T x);
T sig2(const T x, const std::vector<T>& v);

It may also be a function object in which operator() would behave like the free functions. Use of the function template for any of the 4 possibilities should be transparent.

std::vector<int> v;
// ... fill v somehow ...

// foo is either free function or function object instance
const std::vector<int> a = map_vec(foo, v);

I asked how to do this for C++11 and got a great answer from 0x499602D2.

"Overload" function template based on function object operator() signature

0x499602D2's answer makes use of the fact that these are two distinct template signatures in C++11:

template<typename F, typename T>
auto map_vec(F&& fnc, const std::vector<T>& source)
    -> decltype(void(fnc(std::declval<T>())), std::vector<T>{});

template<typename F, typename T>
auto map_vec(F&& fnc, const std::vector<T>& source)
    -> decltype(void(fnc(std::declval<T>(), source)), std::vector<T>{});

I would also like to know how to solve this in C++98.

Here is my effort so far. I have a SFINAE struct that can determine if a function objects takes two args. I don't know how to get this working for both function objects and free functions. Either I need to change the SFINAE struct to work on both function objects and free functions or I need to use overloading to route function objects and free functions separately.

http://coliru.stacked-crooked.com/a/1471088cbc3b8544

Community
  • 1
  • 1
Praxeolitic
  • 22,455
  • 16
  • 75
  • 126
  • Solving this for C++98 indicates an older compiler version. The problem is that older compiler versions often merely support expression SFINAE - partly because name mangling wasn't supporting that stuff a couple of years ago. Try to put the expression into a partial specialization template argument list to avoid at least that. – Columbo Dec 05 '14 at 20:31
  • I just mean the C++98 standard, not the compilers of the era. g++ 4.9 with -std=c++98 is fine. – Praxeolitic Dec 05 '14 at 21:09
  • So what exactly is the point of putting g++ 4.9 in that mode? I.e. what's the practical relevance of creating a solution that only works for compiler versions which support a newer standard (with which we could compose a better solution)? – Columbo Dec 05 '14 at 21:16
  • You can think of C++98 and C++11 as separate but related languages. The job may call for one or the other but you (almost) always want the implementation to be up to date and as correct as possible. – Praxeolitic Dec 05 '14 at 21:30
  • Nonsense. C++11 is an *improved* version of C++98. If you can use it, *do so*. – Columbo Dec 05 '14 at 21:37
  • And what would you say of C? Also obsolete? In all seriousness though, C++11 does introduce breaking changes and so is not strictly "C++98 and more". http://stackoverflow.com/questions/6399615/what-breaking-changes-are-introduced-in-c11. A practical use of C++98 with modern compilers is interfacing with legacy code. This is really off topic though. Maybe you should open a question on Programmers SE? – Praxeolitic Dec 05 '14 at 21:48
  • You're right - otherwise this comment discussion would digress too much. – Columbo Dec 05 '14 at 21:57

1 Answers1

2

Here's my approach:

template <std::size_t, typename T = void> struct ignore_value {typedef T type;};

template <typename T>
T& declval();

template<typename F, typename T>
typename ignore_value<sizeof(declval<F>()(declval<T const>())),
       std::vector<T> >::type map_vec(F fnc, const std::vector<T>& source);

template<typename F, typename T>
typename ignore_value<sizeof(declval<F>()
                         (declval<T const>(), declval<const std::vector<T> >())),
       std::vector<T> >::type map_vec(F fnc, const std::vector<T>& source);

It works with the same Demo that 0x499602D2 used, with both GCC and Clang in C++98 mode.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Could you briefly explain the `declval` part? – Praxeolitic Dec 05 '14 at 23:14
  • I think I get `declval` but why is it used on `T` and `std::vector` too? – Praxeolitic Dec 05 '14 at 23:21
  • @Praxeolitic Because we don't want to a) Unnecessarily require a default constructor for `T`, b) Don't want to restrict our set of accepted `F`'s to ones that can accept a temporary `vector` as the second argument. – Columbo Dec 05 '14 at 23:23
  • Are you sure about b? If I change `declval >()` to `std::vector()` all seems well. I tried with functions that take as value, const value, and const ref and they all compiled. – Praxeolitic Dec 06 '14 at 00:32
  • 1
    @Praxeolitic [Convoluted counterexample for C++98](http://coliru.stacked-crooked.com/a/991e6b79b14731e5). Or for C++11 (in case the code might be ported): A parameter of a class type that has two non-`explicit` constructors: One that has a parameter of type const lvalue reference to `vector<>`, and one that is deleted and has a parameter of type rvalue reference to `vector<>`. [Second demo](http://coliru.stacked-crooked.com/a/47018959a91b36ae). – Columbo Dec 06 '14 at 00:50