3

I struggle to get this right. I want to create overloaded template functions hell that would make such calls possible and correct (GMock):

ASSERT_EQ(min(1, 2), 1);
ASSERT_EQ(min(std::less<>(),3,2), 2);

auto abs_comp = [](auto el1, auto el2){
  return std::abs(el1) < std::abs(el2);
};
ASSERT_EQ(min(abs_comp, -1, -5), -1);
ASSERT_EQ(min(4, 3, 2, 1), 1);

All is good except for this assertion:

ASSERT_EQ(min(std::less<>(), 3,2,1), 2);

And when I extract the function itself to get a meaningful error:

min(std::less<>(), 3,2,1)

I get this:

In file included from /home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/container_minimum_test.cpp:4:
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h: In instantiation of ‘First cppchallenge::lang::min(First, Args ...) [with First = int; Args = {}; <template-parameter-1-3> = std::enable_if<true, void>]’:
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:17:30:   required from ‘First cppchallenge::lang::min(First, Args ...) [with First = std::less<void>; Args = {int}; <template-parameter-1-3> = std::enable_if<false, void>]’
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:17:19:   required from ‘First cppchallenge::lang::min(First, Args ...) [with First = std::less<void>; Args = {int, int, int}; <template-parameter-1-3> = std::enable_if<false, void>]’
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/container_minimum_test.cpp:40:33:   required from here
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:17:30: error: no matching function for call to ‘min()’
         return min(first, min(args...));
                           ~~~^~~~~~~~~
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:8:7: note: candidate: ‘template<class T> T cppchallenge::lang::min(T, T)’
     T min(T first, T second) {
       ^~~
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:8:7: note:   template argument deduction/substitution failed:
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:17:30: note:   candidate expects 2 arguments, 0 provided
         return min(first, min(args...));
                           ~~~^~~~~~~~~
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:16:11: note: candidate: ‘template<class First, class ... Args, class> First cppchallenge::lang::min(First, Args ...)’
     First min(First first, Args... args) {
           ^~~
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:16:11: note:   template argument deduction/substitution failed:
/home/rumcajs/CLionProjects/ModernCppChallenge/tst/lang/../../src/lang/container_minimum.h:17:30: note:   candidate expects at least 1 argument, 0 provided
         return min(first, min(args...));
                           ~~~^~~~~~~~~
gmake[3]: *** [CMakeFiles/ModernCppChallengeLang.dir/build.make:102: CMakeFiles/ModernCppChallengeLang.dir/tst/lang/container_minimum_test.cpp.o] Error 1
gmake[2]: *** [CMakeFiles/Makefile2:116: CMakeFiles/ModernCppChallengeLang.dir/all] Error 2
gmake[1]: *** [CMakeFiles/Makefile2:128: CMakeFiles/ModernCppChallengeLang.dir/rule] Error 2
gmake: *** [Makefile:177: ModernCppChallengeLang] Error 2

The template functions are as below:

namespace cppchallenge::lang {
    //#1
    template<typename T>
    T min(T first, T second) {
        return first < second ? first : second;
    }

    template<typename First, typename... Args>
    using are_same = std::conjunction<std::is_same<First, Args>...>;

    //#2
    template<typename First, typename... Args, typename = std::enable_if<are_same<First, Args...>::value, void>>
    First min(First first, Args... args) {
        return min(first, min(args...));
    }

    //#3
    template<typename Comparator, typename T>
    T min(Comparator comp, T first, T second) {
        return comp(first, second) ? first : second;
    }

    //#4
    template<typename Comparator, typename First, typename... Args,
    typename = std::enable_if<are_same<First, Args...>::value, void>,
    typename std::enable_if<std::is_convertible<Comparator, std::function<bool(First,First)>>::value>::type>
    First min(Comparator comp, First first, Args... args) {
        return min(comp, first, min(comp, args...));
    }
}

The errors are pointing to function #2 though it should use #4.

max66
  • 65,235
  • 10
  • 71
  • 111
Leśny Rumcajs
  • 2,259
  • 2
  • 17
  • 33

1 Answers1

2

I suppose the error is in the following template function: you have to add something as * = nullptr after the last ::type

template<typename Comparator, typename First, typename... Args,
typename = std::enable_if<are_same<First, Args...>::value, void>,
typename std::enable_if<std::is_convertible<Comparator, std::function<bool(First,First)>>::value>::type * = nullptr> // add * = nullptr
First min(Comparator comp, First first, Args... args) {
    return comp(comp, first, min(comp, args...));
}

or also add a typename = before the last typename std::enable_if.

Otherwise, if all goes well (if Firts and all Args... are equal and if Comparable is convertible to the needed st::function, the template signature become

template <typename Comparator, typename First, typename ... Args,
          typename = std::enable_if<are_same<First, Args...>::value,
          void>

and the last void, alone, doesn't make sense (and the preceding std::enable_if isn't much useful; but this is another problem; see the following "bonus suggestion")

You should transform in something similar to

template <typename Comparator, typename First, typename ... Args,
          typename = std::enable_if<are_same<First, Args...>::value, void>
          void * = nullptr>
//............^^^^^^^^^^^^

or also

template <typename Comparator, typename First, typename ... Args,
          typename = std::enable_if<are_same<First, Args...>::value, void>,
          typename = void>
//........^^^^^^^^^^^

Bonus suggestion: the preceding SFINAE test should be

typename = std::enable_if_t<are_same<First, Args...>::value, void>
// ......................^^

or also (the last std::enable_if_t parameter is void by default)

typename = std::enable_if_t<are_same<First, Args...>::value>

otherwise the test never works and the function is ever enabled (from the point of view of the First and Args... types).

Similar problem in the SFINAE test for

First min(First first, Args... args) 
max66
  • 65,235
  • 10
  • 71
  • 111
  • What does it mean? I checked condition `std::is_convertible>::value` and it works. why add `* = nullptr` ? – Amir Rasulov Nov 01 '18 at 10:35
  • @AmirRasulov - yes, `std::is_convertible>::value` itself works; but you have to see it in the context of the template signature of the function; I've improved the answer; I hope that now if more clear. – max66 Nov 01 '18 at 10:38
  • 1
    @LeśnyRumcajs - check also other SFINAE tests: sometime you use `std::enable_if` when you should use `std::enable_if_t` – max66 Nov 01 '18 at 10:49
  • @max66, Thanks for explanation! If I understood problem was with forgotten `=` in `typename std::enable_if>::value>::type>` ? – Amir Rasulov Nov 01 '18 at 11:04
  • @max66 That's what I call a cherry pick. I wasn't aware of this helper type. Got rid of these cryptic voids. Thanks again. – Leśny Rumcajs Nov 01 '18 at 11:05
  • 1
    @AmirRasulov - with forgotten `typename =` before of forgotten `* = nullptr` after; works in both ways; what doesn't works is an isolated `void`. – max66 Nov 01 '18 at 12:18