【问题标题】:enum, declaration outside of namespace and namespace causes "call of overloaded (...) is ambiguous"枚举,命名空间和命名空间之外的声明导致“重载(...)的调用不明确”
【发布时间】:2021-04-15 13:57:29
【问题描述】:

采取以下措施:

foo.h

#ifndef FOO_H
#define FOO_H

#include <stdint.h>

enum directions {
    NORTH,
    EAST,
    SOUTH,
    WEST
};

bool foo_a(uint16_t a);
bool foo_b(enum directions a);

#endif //FOO_H

foo.cpp

#include "foo.h"

#include <iostream>

namespace test {
    bool foo_a(uint16_t a) {
        return a < 22;
    }

    bool foo_b(enum directions a) {
        return a < WEST;
    }

    bool foo(void) {
        return foo_a(16) || foo_b(NORTH);
    }
}

int main(void)
{
    std::cout << test::foo();
    return 0;
}

如果我们尝试编译它,我们会得到错误:

g++ foo.cpp
foo.cpp: In function ‘bool test::foo()’:
foo.cpp:17:40: error: call of overloaded ‘foo_b(directions)’ is ambiguous
   15 |         return foo_a(16) || foo_b(NORTH);
      |                                        ^
foo.cpp:12:10: note: candidate: ‘bool test::foo_b(directions)’
   10 |     bool foo_b(enum directions a) {
      |          ^~~~~
In file included from foo.cpp:2:
foo.h:14:6: note: candidate: ‘bool foo_b(directions)’
   14 | bool foo_b(enum directions a);
      |

因此编译器会抱怨foo_b 重载,但不会抱怨foo_a 重载。为什么它完全抱怨foo_b?为什么它不只选择这个命名空间中的那个?为什么enum 的处理方式与uint16_t 不同(foo_a 没有错误)?

【问题讨论】:

  • ADL 最有可能应用于枚举函数,但由于uint16_t 最有可能映射到内置类型,因此不会发生 ADL。
  • @Eljay 我知道如何解决它,这是为什么让我感到困惑的问题。
  • 啊,好吧。内森奥利弗所说的。由于 ADL,foo_b 不明确。 foo_a 不是由于 ADL 而产生歧义,因为 ADL 不适用。

标签: c++ enums namespaces


【解决方案1】:

@NathanOliver 已经解释了原因。我会尝试详细说明。

首先,这里有一个示例说明编译器可能会混淆的原因:

#include <stdint.h>

enum directions {
    NORTH,
    EAST,
    SOUTH,
    WEST
};

bool foo_a(uint16_t a);
bool foo_b(enum directions a);

namespace test {

    bool foo_a(uint16_t a) {
        return a < 22;
    }

    bool foo_b(enum directions a) {
        return a < WEST;
    }

    bool foo(void) {
        return foo_a(16) || foo_b();
    }
}

bool foo_b(enum directions a) {
    return a != SOUTH;
}

在此示例中,编译器可能不清楚foo 是否应该调用命名空间之外的test::foo_bfoo_b。编译器按顺序遍历文件,因此当编译器分析从foofoo_b 的调用时,它无法知道foo_b 的定义是否在@987654329 的范围之外@。

然而,编译器对foo_a 没有任何问题。这是因为如果编译器有几个选项,它会更喜欢在最内部范围内声明的选项,在这种情况下:test::foo_a

但是,编译器首先搜索函数的命名空间和类的集合也会受到其他因素的影响,例如传递给函数的参数类型。根据this manual

  1. 对于枚举类型的参数,最里面的封闭命名空间 枚举类型的声明被定义被添加到 放。如果枚举类型是类的成员,则该类是 添加到集合中。

因此,由于foo 使用NORTH 类型为enum direction 的参数调用foo_b,因此定义enum direction 的范围对于编译器而言变得与内部命名空间test 一样“重要” (它被添加到“关联的命名空间和类集”中),它们变得同样重要,因此编译器会在两个 foo_b 函数之间混淆,无法决定使用哪个。

这就是为什么,在下面的两个例子中,编译器没有问题。在第一个示例中,foo 传递给foo_bNORTHenum test::direction 类型,因此外部范围不会添加到编译器的“关联的命名空间和类集”中。在第二个示例中,foo 调用 foo_b 没有显式传递参数,因此再次没有添加外部作用域。

示例 1:

#include <stdint.h>

enum directions {
    NORTH,
    EAST,
    SOUTH,
    WEST
};

bool foo_a(uint16_t a);
bool foo_b(enum directions a);

namespace test {

    enum directions {
        NORTH,
        EAST,
        SOUTH,
        WEST
    };

    bool foo_a(uint16_t a) {
        return a < 22;
    }

    bool foo_b(enum directions a) {
        return a < WEST;
    }

    bool foo(void) {
        return foo_a(16) || foo_b(NORTH);
    }
}

示例 2:

#include <stdint.h>

enum directions {
    NORTH,
    EAST,
    SOUTH,
    WEST
};

bool foo_a(uint16_t a);
bool foo_b(enum directions a = NORTH);

namespace test {

    bool foo_a(uint16_t a) {
        return a < 22;
    }

    bool foo_b(enum directions a = NORTH) {
        return a < WEST;
    }

    bool foo(void) {
        return foo_a(16) || foo_b();
    }
}

【讨论】:

    猜你喜欢
    • 2011-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 1970-01-01
    • 2020-01-14
    • 2011-06-14
    相关资源
    最近更新 更多