当重载具有多重继承的函数时,GCC称调用它是不明确的,但Clang和MSVC不这样做

时间:2017-12-22 03:29:36

标签: c++ g++ c++17 clang++

我正在使用此变体库:https://github.com/cbeck88/strict-variant。它提供了类似于- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSError *error; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *destinationFileName = downloadTask.originalRequest.URL.lastPathComponent; NSURL *destinationURL = [self.downloadDirURL URLByAppendingPathComponent:destinationFileName]; if([fileManager fileExistsAtPath:[destinationURL path]]) { [fileManager removeItemAtURL:destinationURL error:nil]; } BOOL success = [fileManager moveItemAtURL:location toURL:destinationURL error:&error]; } std::variant的类。鉴于此boost::variant

struct

我想这样做:

struct S
{
    explicit S(double) {}
};

这适用于Clang 5.0.1和MSVC 19.12.25831.00,但无法使用GCC 7.2.1进行编译。

我查看了图书馆的代码并将问题解决了:

strict_variant::variant<double, S> v = 2.0;

输出为

#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] 的定义更改为:

时,它适用于GCC(仍然是Clang和MSVC)
initializer

我对C ++的理解认为这是等价的,所以我认为这是GCC中的一个错误,但我经常遇到标准说出令人惊讶的事情并且我的假设是错误的问题。所以,我的问题是:谁的错是这个? GCC是否有错误,Clang和MSVC是否有错误,或者是未定义/未指定的代码解释,以便所有编译器都正确?如果代码错了,怎么修复?

1 个答案:

答案 0 :(得分:7)

这实际上是一个铿锵的错误。

经验法则是不同范围内的名称不会超载。这是一个简化的例子:

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状态基本上只有一个基类可以包含给定的名称。如果多个基类具有相同的名称,则查找是不明确的。这里的解决方案是使用using-declaration将两个基类名称引入派生类。在C ++ 17中,使用声明可以是包扩展,这使得这个问题变得更加容易:

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标准的修复程序......这只是更冗长一点。)