当重载具有多重继承的函数时,GCC 说调用它是不明确的,但 Clang 和 MSVC 没有
When overloading a function with multiple inheritance, GCC says calling it is ambiguous, but Clang and MSVC do not
我正在使用这个变体库:https://github.com/cbeck88/strict-variant。它提供了一个类似于 std::variant
和 boost::variant
的 class。鉴于此 struct
:
struct S
{
explicit S(double) {}
};
我想这样做:
strict_variant::variant<double, S> v = 2.0;
这适用于 Clang 5.0.1 和 MSVC 19.12.25831.00,但无法使用 GCC 7.2.1 进行编译。
我查看了库的代码并将问题简化为:
#include <iostream>
struct S
{
constexpr S() {}
constexpr explicit S(double) {}
};
template<unsigned i> struct init_helper;
template<> struct init_helper<0> { using type = double; };
template<> struct init_helper<1> { using type = S; };
template<unsigned i>
struct initializer_leaf
{
using target_type = typename init_helper<i>::type;
constexpr unsigned operator()(target_type) const
{
return i;
}
};
struct initializer : initializer_leaf<0>, initializer_leaf<1>
{
};
int main()
{
std::cout << initializer()(double{}) << " = double" << '\n';
std::cout << initializer()(S{}) << " = S" << '\n';
return 0;
}
输出为
0 = double
1 = S
海湾合作委员会说:
strict_variant_test.cpp: In function ‘int main()’:
strict_variant_test.cpp:29:37: error: request for member ‘operator()’ is ambiguous
std::cout << initializer()(double{}) << " = double" << '\n';
^
strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S]
constexpr unsigned operator()(target_type) const
^~~~~~~~
strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double]
strict_variant_test.cpp:30:32: error: request for member ‘operator()’ is ambiguous
std::cout << initializer()(S{}) << " = S" << '\n';
^
strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S]
constexpr unsigned operator()(target_type) const
^~~~~~~~
strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double]
但是,当我将 initializer
的定义更改为:
时,它可以与 GCC(以及 Clang 和 MSVC)一起使用
struct initializer
{
constexpr unsigned operator()(double) const
{
return 0;
}
constexpr unsigned operator()(S) const
{
return 1;
}
};
我对 C++ 的理解表明这是等价的,所以我假设这是 GCC 中的错误,但我经常 运行 遇到标准说出令人惊讶的事情而我的假设是错误的问题。所以,我的问题是:这是谁的错? GCC 是否有错误,Clang 和 MSVC 是否有错误,或者代码 undefined/unspecified 的解释是否所有编译器都是正确的?如果代码错误,如何修复?
这实际上是一个 clang 错误。
根据经验,不同 范围内的名称不会超载。这是一个简化的例子:
template <typename T>
class Base {
public:
void foo(T ) { }
};
template <typename... Ts>
struct Derived: Base<Ts>...
{};
int main()
{
Derived<int, double>().foo(0); // error
}
这应该是一个错误,因为 class member lookup rules 声明基本上只有一个碱基 class 可以包含给定名称。如果多个碱基 class 具有相同的名称,则查找不明确。此处的解决方案是使用 using 声明将 both 基础 class 名称带入派生的 class 中。在 C++17 中,using 声明可以是一个包扩展,这使得这个问题变得容易得多:
template <typename T>
class Base {
public:
void foo(T ) { }
};
template <typename... Ts>
struct Derived: Base<Ts>...
{
using Base<Ts>::foo...;
};
int main()
{
Derived<int, double>().foo(0); // ok! calls Base<int>::foo
}
对于特定的库,this code:
template <typename T, unsigned... us>
struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... {
static_assert(sizeof...(us) > 0, "All value types were inelligible!");
};
应该看起来像:
template <typename T, unsigned... us>
struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... {
static_assert(sizeof...(us) > 0, "All value types were inelligible!");
using initializer_leaf<T, us>::operator()...; // (*) <==
};
(虽然我猜这个库是针对 C++11 的,所以我提交了一个符合 C++11 的修复程序……只是有点冗长)。
我正在使用这个变体库:https://github.com/cbeck88/strict-variant。它提供了一个类似于 std::variant
和 boost::variant
的 class。鉴于此 struct
:
struct S
{
explicit S(double) {}
};
我想这样做:
strict_variant::variant<double, S> v = 2.0;
这适用于 Clang 5.0.1 和 MSVC 19.12.25831.00,但无法使用 GCC 7.2.1 进行编译。
我查看了库的代码并将问题简化为:
#include <iostream>
struct S
{
constexpr S() {}
constexpr explicit S(double) {}
};
template<unsigned i> struct init_helper;
template<> struct init_helper<0> { using type = double; };
template<> struct init_helper<1> { using type = S; };
template<unsigned i>
struct initializer_leaf
{
using target_type = typename init_helper<i>::type;
constexpr unsigned operator()(target_type) const
{
return i;
}
};
struct initializer : initializer_leaf<0>, initializer_leaf<1>
{
};
int main()
{
std::cout << initializer()(double{}) << " = double" << '\n';
std::cout << initializer()(S{}) << " = S" << '\n';
return 0;
}
输出为
0 = double
1 = S
海湾合作委员会说:
strict_variant_test.cpp: In function ‘int main()’:
strict_variant_test.cpp:29:37: error: request for member ‘operator()’ is ambiguous
std::cout << initializer()(double{}) << " = double" << '\n';
^
strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S]
constexpr unsigned operator()(target_type) const
^~~~~~~~
strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double]
strict_variant_test.cpp:30:32: error: request for member ‘operator()’ is ambiguous
std::cout << initializer()(S{}) << " = S" << '\n';
^
strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S]
constexpr unsigned operator()(target_type) const
^~~~~~~~
strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double]
但是,当我将 initializer
的定义更改为:
struct initializer
{
constexpr unsigned operator()(double) const
{
return 0;
}
constexpr unsigned operator()(S) const
{
return 1;
}
};
我对 C++ 的理解表明这是等价的,所以我假设这是 GCC 中的错误,但我经常 运行 遇到标准说出令人惊讶的事情而我的假设是错误的问题。所以,我的问题是:这是谁的错? GCC 是否有错误,Clang 和 MSVC 是否有错误,或者代码 undefined/unspecified 的解释是否所有编译器都是正确的?如果代码错误,如何修复?
这实际上是一个 clang 错误。
根据经验,不同 范围内的名称不会超载。这是一个简化的例子:
template <typename T>
class Base {
public:
void foo(T ) { }
};
template <typename... Ts>
struct Derived: Base<Ts>...
{};
int main()
{
Derived<int, double>().foo(0); // error
}
这应该是一个错误,因为 class member lookup rules 声明基本上只有一个碱基 class 可以包含给定名称。如果多个碱基 class 具有相同的名称,则查找不明确。此处的解决方案是使用 using 声明将 both 基础 class 名称带入派生的 class 中。在 C++17 中,using 声明可以是一个包扩展,这使得这个问题变得容易得多:
template <typename T>
class Base {
public:
void foo(T ) { }
};
template <typename... Ts>
struct Derived: Base<Ts>...
{
using Base<Ts>::foo...;
};
int main()
{
Derived<int, double>().foo(0); // ok! calls Base<int>::foo
}
对于特定的库,this code:
template <typename T, unsigned... us> struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... { static_assert(sizeof...(us) > 0, "All value types were inelligible!"); };
应该看起来像:
template <typename T, unsigned... us>
struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... {
static_assert(sizeof...(us) > 0, "All value types were inelligible!");
using initializer_leaf<T, us>::operator()...; // (*) <==
};
(虽然我猜这个库是针对 C++11 的,所以我提交了一个符合 C++11 的修复程序……只是有点冗长)。