【问题标题】:Weird constructor SFINAE error with std::initializer_list带有 std::initializer_list 的奇怪构造函数 SFINAE 错误
【发布时间】:2021-02-07 20:31:20
【问题描述】:

我不明白为什么下面的代码不能编译。编译器给出的错误信息也没什么帮助。

工作示例:

#include <string>
#include <type_traits>

template <typename T>
struct TIteratorValue {
    using Type = typename T::Type;
};

template <typename T>
struct TIteratorValue<T *> {
    using Type = T;
};

template <typename T>
using IteratorValue = typename TIteratorValue<T>::Type;

template <typename T>
struct Test {
    template <typename V,
              std::enable_if_t<std::is_constructible_v<T, V>, int> = 0>
    Test(std::initializer_list<V> const list, size_t const x = 0)
        : Test(list.begin(), list.end(), x) {}

    template <typename I,
              std::enable_if_t<std::is_constructible_v<T, IteratorValue<I>>, int> = 0>
    // Does not compile!
    Test(I begin, I const end, size_t const x = 0) {}
    // Compiles!
    //Test(I begin, I const end, size_t const x) {}
};

int
main() {

    Test<std::string> test({ "a", "b", "c" }, 10);

    return 0;
}

Clang 的错误信息:

C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:6:24: error: type 'int' cannot be used prior to '::' because it has no members     
        using Type = typename T::Type;
                              ^
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:15:1: note: in instantiation of template class 'TIteratorValue<int>' requested here
using IteratorValue = typename TIteratorValue<T>::Type;
^
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:25:50: note: in instantiation of template type alias 'IteratorValue' requested here
                          std::enable_if_t<std::is_constructible_v<T, IteratorValue<I>>, int> = 0>
                                                                      ^
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:27:2: note: while substituting prior template arguments into non-type template parameter
      [with I = int]
        Test(I begin, I const end, size_t const x = 0) {}
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:35:20: note: while substituting deduced template arguments into function template 'Test'
      [with I = int, $1 = (no value)]
        Test<std::string> test({ "a", "b", "c" }, 10);

唯一的int 是参数x,但我看不出它对代码有何影响。 Visual Studio 也给了我同样的错误。

【问题讨论】:

    标签: c++ sfinae initializer-list overload-resolution constructor-overloading


    【解决方案1】:
    template <typename I, std::enable_if_t<std::is_constructible_v<T, IteratorValue<I>>, int> = 0>
    // Does not compile!
    Test(I begin, I const end, size_t const x = 0) {}
    // Compiles!
    //Test(I begin, I const end, size_t const x) {}
    

    如果模板被实例化,这两个函数都不会编译

    由于您的 main 函数尝试从 2 个参数构造 Test,因此向第三个参数添加默认值仅意味着应该考虑此函数。它允许实例化模板。

    我还是不明白为什么下面的代码不能编译。

    您正在定义IteratorValue&lt;I&gt;(因此是I::type检查I 是否是一个迭代器。

    使用已经定义的std::iterator_traits 来解决这个问题。

    // Formatted for clarity
    template <typename I,
              std::enable_if_t<
                  std::is_constructible_v<
                      T,
                      typename std::iterator_traits<I>::value_type
                  >,
                  int
              > = 0>
    

    【讨论】:

    • 如果编译器选择第二个构造函数,这是否意味着它将大括号初始化列表隐式转换为 int?因为第二个构造函数要求前 2 个参数的类型相同,而其中一个显然不是 int。
    • @JoãoPires 一个大括号初始化列表的参数会抑制模板参数推导(除非对应的参数是std::initializer_list),所以I 是从第二个参数推导出来的。然后编译器没有意识到{ "a", "b", "c" } 不能转换为int,它更早地出错了。我仍然不明白的是,为什么它不会简单地放弃这种过载,由于 SFINAE,一旦它开始尝试int::type
    • 对我来说,在这种情况下,编译器无法判断是没有意义的,大括号初始化列表和int 显然不是同一类型,它应该只是跳过完全重载......哦,好吧......C ++恶作剧。
    • @JoãoPires 除了说std::initializer_list 打破了许多普遍存在的规则之外,我无法提供更深入的理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 2015-01-31
    • 2021-06-28
    • 2015-04-07
    • 1970-01-01
    相关资源
    最近更新 更多