10

In the below code, the compiler can't figure out which constructor I want to use. Why, and how do I fix this? (Live example)

#include <tuple>
#include <functional>
#include <iostream>

template<typename data_type, typename eval_type, typename Type1, typename Type2>
class A
{
public:
    using a_type = std::tuple<Type1, Type2>;
    using b_type = std::tuple<std::size_t,std::size_t>;

    inline explicit constexpr A(const std::function<data_type(a_type)>& Initializer,
        const std::function<eval_type(data_type)>& Evaluator,
        const Type1& elem1, const Type2& elem2)
    {
        std::cout << "idx_type" << std::endl;
    }
    inline explicit constexpr A(const std::function<data_type(b_type)>& Initializer,
        const std::function<eval_type(data_type)>& Evaluator,
        const Type1& elem1, const Type2& elem2)
    {
        std::cout << "point_type" << std::endl;
    }
};

int main()
{
    int a = 1;
    long long b = 2;
    auto c = A<double, double, long long, int>{
        [](std::tuple<long long,int> p)->double { return 1.0*std::get<0>(p) / std::get<1>(p); },
        [](double d)->double { return d; }, b,a
        };

    return 0;
}
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
AOK
  • 493
  • 5
  • 16
  • 3
    Thanks for the live MCVE! – Quentin Mar 14 '18 at 14:45
  • lambda are not `std::function`. – Jarod42 Mar 14 '18 at 14:47
  • 2
    I had to reread the constructor parameters 3 times before I noticed `a_type` and `b_type` are different. – Hatted Rooster Mar 14 '18 at 14:47
  • 3
    Short answer is that both constructors can be used because [the fifth](http://en.cppreference.com/w/cpp/utility/functional/function/function) overload of `std::function`. – Hatted Rooster Mar 14 '18 at 14:49
  • 3
    A [mcve] will actually look more like [this](http://coliru.stacked-crooked.com/a/d03fb0862739eec4) – Passer By Mar 14 '18 at 14:52
  • @SombreroChicken: And that `std::tuple` and `std::tuple` are constructible from each other. – Jarod42 Mar 14 '18 at 14:57
  • Which constructor do you want to use? – Jive Dadson Mar 14 '18 at 14:57
  • I would like to use the one with `b_type` – AOK Mar 14 '18 at 14:57
  • @Jarod42 Good addition, thanks. – Hatted Rooster Mar 14 '18 at 14:58
  • @SobreroChicken, but i thought the using the keyword `explicit` should prevent the conversion from `std::tuple` to `std::tuple` – AOK Mar 14 '18 at 14:58
  • Another workaround alternative is to not use a tuple for the b_type, but instead roll your own. `using b_type = struct { std::size_t a; std::size_t b; };` – Eljay Mar 14 '18 at 14:59
  • thanks Eljay, however I want to keep the structure considering how all these elements fit together. Also, I give a particular example with 2 types, but my original class uses a variadic list of types. At that point, the struct solution is far less workable and requires doubling the code (one for use with a_type and one with b_type) – AOK Mar 14 '18 at 15:03
  • @AOK "... _should prevent the conversion from `std::tuple` to `std::tuple`_" It does if you change that converting constructor to a one argument constructor callable only by direct initialization, by marking it `explicit`, or if you remove that implicit conversion operator, whichever it is that perform the conversion (I haven't checked and don't want to, as it's irrelevant for the point I'm making). But then of course `std::tuple` is not a customization point, you can't change it. – curiousguy Jun 13 '18 at 00:58

2 Answers2

4

The reason it doesn't work is because a lambda is not a std::function and so the compiler tries to create one using the fifth overload of the constructor. The problem is that both of your A constructors can be used because of this conversion and the reason that std::tuple<long long,int> and std::tuple<std::size_t,std::size_t> are constructible from each other makes this ambigious for the compiler what constructor to pick.

What you could do is explicitly cast to the desired std::function (MCVE of @PasserBy used in comments), like this:

#include <tuple>
#include <functional>
#include <iostream>

template<typename data_type, typename Type1, typename Type2>
class A
{
public:
    using a_type = std::tuple<Type1, Type2>;
    using b_type = std::tuple<std::size_t,std::size_t>;

    A(const std::function<data_type(a_type)>&)
    {
        std::cout << "idx_type" << std::endl;
    }
    A(const std::function<data_type(b_type)>&)
    {
        std::cout << "point_type" << std::endl;
    }
};

int main()
{
    std::function<double(std::tuple<long long, int>)> func = [](auto p) -> double { return 1; };
    auto c = A<double, long long, int>{
        func
    };
}
Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • I was hoping for some solution that mucks around with `class A`, but this will have to do for now. Thanks! – AOK Mar 14 '18 at 15:07
1

As @SombreroChicken mentioned, std::function<R(Args...)> has a constructor that allows any callable object c to initialize it, as long as c(Args...) is valid and returns something convertible to R.

To fix it, you may use some SFINAE machinery

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

template<typename data_type, typename Type1, typename Type2>
class A
{
    template<typename T>
    struct tag
    {
        operator T();
    };

public:
    using a_type = std::tuple<Type1, Type2>;
    using b_type = std::tuple<std::size_t,std::size_t>;

    template<typename C, std::enable_if_t<std::is_invocable_v<C, tag<b_type>>>* = nullptr>
    A(C&& initializer)
    {
        std::cout << "size_t" << std::endl;
    }

    template<typename C, std::enable_if_t<std::is_invocable_v<C, tag<a_type>>>* = nullptr>
    A(C&& initializer)
    {
        std::cout << "other" << std::endl;
    }
};

int main()
{
    auto c = A<double, long long, int>{
        [](std::tuple<long long, int> p) -> double { return 1; }
    };

    auto c2 = A<double, long long, int>{
        [](std::tuple<std::size_t, std::size_t>) -> double { return 2; }  
    };
}

Live

Here, we turn off the constructor if the callable can be called with b_type or a_type respectively. The extra indirection through tag is there to disable the conversion between tuples of different types

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • I've been trying to put this together for VC++, but `std::is_invocable_v` isn't available and I can't get `std::_Is_invocable_r` to work. Any suggestions? – AOK Mar 14 '18 at 16:08
  • @AOK It seems like MSVC doesn't have this yet. You may try implement it yourself. A possible implementation is to first implement [`invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke) and SFINAE on whether the invocation is successful – Passer By Mar 14 '18 at 16:20
  • I tried to take a stab at it from a different angle (mostly since I had the structures for it already built), but it doesn't seem to be working. I posted it up as another questions because I feel it might be sufficiently different, in case you want to take a peek: [here](https://stackoverflow.com/questions/49286989/how-to-properly-use-stdenable-if-in-c-on-a-constructor) – AOK Mar 14 '18 at 20:22