为什么此C++代码中的构造函数模棱两可以及如何修复它



在下面的代码中,编译器无法确定我想使用哪个构造函数。为什么,我该如何解决这个问题?(现场示例(

#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;
}

它不起作用的原因是因为 lambda 不是std::function,因此编译器尝试使用构造函数的第五个重载创建一个。问题在于,由于这种转换,可以使用两个A构造函数,并且std::tuple<long long,int>std::tuple<std::size_t,std::size_t>可以相互构造的原因使得编译器选择哪个构造函数变得模棱两可。

您可以做的是显式转换为所需的std::function(注释中使用的@PasserBy MCVE(,如下所示:

#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
    };
}

正如@SombreroChicken提到的,std::function<R(Args...)>有一个构造函数,允许任何可调用的对象c初始化它,只要c(Args...)有效并返回可转换为R的东西。

要修复它,您可以使用一些 SFINAE 机器

#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; }  
    };
}

在这里,如果可以分别使用 b_typea_type 调用可调用对象,我们将关闭构造函数。通过tag的额外间接用于禁用不同类型的元组之间的转换

最新更新