我想要一个函数重载,其中一个用例通过指针传递非数组参数,另一个用例通过引用传递 C 数组参数。

不幸的是,传递 C 数组参数会选择两个候选函数,因为 C 数组的指针衰减必须处于相同的考虑级别,从而使情况变得模棱两可。

#include <iostream>

static void foo(int* p) {
    std::cout << "int* -> " << *p << "\n";
}

static void foo(int (&a)[4]) {
    std::cout << "int[4] -> "
        << a[0] << " "
        << a[1] << " "
        << a[2] << " "
        << a[3] << "\n";
}

int main() {
    int array[4]{ 10, 20, 30, 40 };
    foo(array); // error: call to 'foo' is ambiguous
    foo(array + 3);
}

我目前正在使用此解决方法。我对此解决方法不满意。

static void foo(int (*a)[4]) { /*...*/ }

…这将通过以下方式调用…

    foo(&array);

将传递指针改为传递引用到指针的替代方法void foo(int*& p)将会阻止调用它,foo(array + 3)因为那是一个右值。

是否有另一种方法(或模板魔法)可以消除这两种情况的歧义?

上下文最好是 C++17,但如果可以用 C++20 或更高版本来表达一个不错的解决方案,那仍然会引起人们的兴趣。

(XY 问题?)原始上下文是将指针或数组作为类构造函数参数传递。原始上下文是尝试制作一个“胖指针”,就像 Google 的 Miracle Pointer 一样,它是常规原始指针的指针替代品,但包含跨度和偏移信息以及偏执检查(这确实会产生额外的运行时开销)。我试图将示例简化为突出且最小的再现案例。

更新(回复评论):最终,我希望它也支持并区分 aconst int[4]和 a const int*

10

  • 2
    (不是你想要的,但是:)摆脱内置数组 complete 并将它们全部替换为std::array。它们的行为更加合理。在函数参数int (*a)[4]中用相应的固定长度替换std::span


    – 


  • 4
    如果你让指针重载模板(例如template<typename=void>),那么它在歧义方面将被视为更差的候选者。


    – 


  • 1
    指针插入替换可以是具有适当重载的普通类类型。它不需要依赖内置数组或指针。标准库甚至有此类类型的概念,并在分配器接口等中支持它们。(有些东西不能完全插入,例如,如果用户代码使用std::is_pointer,明确写出对内置指针类型的引用等,但无论如何你都有这个问题。)


    – 


  • 这里的缺少const重要吗?


    – 

  • 如果允许的话那将是一种邪恶的超负荷


    – 


最佳答案
2

调度员在if constexpr (std::is_array工作。

#include <iostream>
#include <type_traits>

static void fooin(int* p) {
    std::cout << "int* -> " << *p << "\n";
}

static void fooinarr(int (&a)[4]) {
    std::cout << "int[4] -> "
        << a[0] << " "
        << a[1] << " "
        << a[2] << " "
        << a[3] << "\n";
}

template<typename T>
static void foo(T &&a) {
    if constexpr (std::is_array_v<std::remove_reference_t<T>>) {
        fooinarr(std::forward<T>(a));
    } else {
        fooin(std::forward<T>(a));
    }
}

int main() {
    int array[4]{ 10, 20, 30, 40 };
    foo(array); // error: call to 'foo' is ambiguous
    foo(array + 3);
}

或相同:

template<typename T, std::enable_if_t<std::is_array_v<std::remove_reference_t<T>>, int> = 0>
static void foo(T &&a) {
        fooinarr(std::forward<T>(a));
}

template<typename T, std::enable_if_t<!std::is_array_v<std::remove_reference_t<T>>, int> = 0>
static void foo(T &&a) {
        fooin(std::forward<T>(a));
}

1

  • 标记为“相同”的第二部分最适合我的实际代码。C++ 模板相当难懂。第一个建议和附录建议都非常有用,谢谢。


    – 

不幸的是,传递 C 数组参数会选择两个候选函数

因此,您应该人为地使非数组构造函数变得更糟。最简单的方法已在@user17732522 的评论中提供。但如果出于某种原因该方法对您不起作用,这里有一个较旧的方法:引入用户定义的转换。

template <typename T>
struct RawPtr
{
    [[gnu::always_inline]] constexpr RawPtr(T* ptr)
     : m_ptr(ptr)
    {
    }

    T*      m_ptr;
};

template <typename T>
struct FatPtr
{
    FatPtr(RawPtr<T> p);
    
    template <size_t N>
    FatPtr(T (&p)[N]);
};

template <typename T>
FatPtr(T*) -> FatPtr<T>;

int main()
{
    int         arr[5] = {};
    const char  carr[3] = {};

    FatPtr      t1(arr);
    FatPtr      t2(arr + 3);
    FatPtr      t3(carr);
    FatPtr      t4(carr + 1);
}

实例: https:

1

  • 非常有用的技术。我首先尝试了这个,因为我喜欢这个容易理解的方法,但我最终使用了 KamilCuk 建议的第二种技术。(此外,KamilCuk 的回答更直接地回答了我的问题,但您的回答适合我的实际问题。)


    –