为啥此 C++ 代码中的构造函数不明确,我该如何解决?

Posted

技术标签:

【中文标题】为啥此 C++ 代码中的构造函数不明确,我该如何解决?【英文标题】:Why is the constructor in this C++ code ambiguous and how do I fix it?为什么此 C++ 代码中的构造函数不明确,我该如何解决? 【发布时间】:2018-08-23 03:43:35 【问题描述】:

在下面的代码中,编译器无法确定我要使用哪个构造函数。为什么,我该如何解决这个问题? (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;

【问题讨论】:

感谢直播 MCVE! lambda 不是std::function 我不得不重新读取构造函数参数 3 次,然后才注意到 a_typeb_type 不同。 简短的回答是这两个构造函数都可以使用,因为the fifth 重载了std::function minimal reproducible example 实际上看起来更像this 【参考方案1】:

它不起作用的原因是因为 lambda 不是 std::function,因此编译器尝试使用构造函数的 the fifth 重载来创建一个。问题是,由于这种转换,您的两个 A 构造函数都可以使用,而且 std::tuple&lt;long long,int&gt;std::tuple&lt;std::size_t,std::size_t&gt; 可以相互构造,这使得编译器选择什么构造函数变得模棱两可。

你可以做的是显式转换为所需的std::function(@PasserBy 的 MCVE 在 cmets 中使用)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
    ;

【讨论】:

我希望找到一些与class A 混在一起的解决方案,但现在必须这样做。谢谢!【参考方案2】:

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

要修复它,您可以使用一些 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;   
    ;

Live

这里,如果 callable 可以分别用b_typea_type 调用,我们关闭构造函数。通过tag 的额外间接是为了禁用不同类型的元组之间的转换

【讨论】:

我一直在尝试将它放在一起用于 VC++,但 std::is_invocable_v 不可用,我无法让 std::_Is_invocable_r 工作。有什么建议吗? @AOK 好像 MSVC 还没有这个。您可以尝试自己实现它。一种可能的实现方式是先实现invoke和SFINAE判断调用是否成功 我试图从不同的角度尝试一下(主要是因为我已经为它建造了结构),但它似乎不起作用。我把它作为另一个问题发布,因为我觉得它可能有很大的不同,如果你想看看:here

以上是关于为啥此 C++ 代码中的构造函数不明确,我该如何解决?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不继承 C++ 构造函数?

为啥 C++ 中 std::mutex 的构造函数不抛出?

在 c++ 中的菱形问题中,为啥我们需要从子类中调用祖父构造函数?

为啥看似明确的类型提示构造函数调用存在“没有匹配的 ctor”?

C++:调用无参数的构造函数为啥不加括号

为啥 C++ 构造函数在继承中需要默认参数?