【问题标题】:How to use SFINAE to enable a certain std::initializer_list constructor only for suitable types如何使用 SFINAE 为合适的类型启用某个 std::initializer_list 构造函数
【发布时间】:2021-01-26 16:51:27
【问题描述】:

您可以在下面找到一个精简的、可重复的示例,说明我正在尝试完成的工作。我的用例是一个自定义容器类模板。该类有一个构造函数,采用元素类型的std::initializer_list。对于容器包含std::unique_ptr<OwnedType> 类型元素的情况,我想启用另一个构造函数,该构造函数采用std::initialiser_list<typename std::unique_ptr<OwnedType>::pointer> aka std::initializer_list<OwnedType*>。我的方法是使用 SFINAE 禁用该构造函数,以防我的容器类的元素类型没有名为 pointer 的类型,根据我对 @ 下列出的示例的理解,这应该是一个有效的 SFINAE 失败案例并且没有编译错误987654321@

#include <vector>
#include <memory>

template <typename T>
struct MyVector
{
    /** General initializer list constructor */
    MyVector (std::initializer_list<T> il) : vector (il) {}

    /** Constructor for the case MyVector<std::unique_ptr<SomeType>>. 
        Should be treated as an SFINAE failure for non unique_ptr types 
    */
    template <typename OwnedTypePtr = typename T::pointer>
    MyVector (std::initializer_list<OwnedTypePtr> il)
    {
        vector.reserve (il.size());
        for (auto* ptr : il)
            vector.emplace_back (ptr);
    }

    std::vector<T> vector;
};

template <typename T>
using UniqueVector = MyVector<std::unique_ptr<T>>;

int main()
{
    UniqueVector<int> uv { new int (0), new int (1), new int (2) };
    MyVector<int> v { 0, 1, 2 };

    return 0;
}

(在godbolt.org上使用clang 10.0.0时编译失败)

我似乎在这里理解了一些错误,因为 clang 抱怨说

type 'int' cannot be used prior to '::' because it has no members
in instantiation of template class 'MyVector<int>' requested here
    MyVector<int> v { 0, 1, 2 };

所以我预期的有效 SFINAE 失败案例被解释为编译错误。我很感兴趣为什么上面的代码不是有效的 SFINAE 构造以及如何正确执行它的解决方案。

================================================ =======================

编辑:最初的问题可以通过@super给出的答案来解决。不幸的是,现在在我的现实世界场景中,我遇到了std::complex 值向量的问题,这些向量由标量值初始化,在这些更改之前有效。调整示例

#include <vector>
#include <memory>
#include <complex>

template <typename T>
struct MyVector
{
    /** General initializer list constructor */
    MyVector (std::initializer_list<T> il) : vector (il) {}

    /** Constructor for the case MyVector<std::unique_ptr<SomeType>>. 
        Should be treated as an SFINAE failure for non unique_ptr types 
    */
    template <typename U = T, typename OwnedTypePtr = typename U::pointer>
    MyVector (std::initializer_list<OwnedTypePtr> il)
    {
        vector.reserve (il.size());
        for (auto* ptr : il)
            vector.emplace_back (ptr);
    }

    std::vector<T> vector;
};

template <typename T>
using UniqueVector = MyVector<std::unique_ptr<T>>;

int main()
{
    UniqueVector<int> uv { new int (0), new int (1), new int (2) };
    MyVector<std::complex<int>> v { 0, 1, 2 };

    return 0;
}

live demo on godbolt

我不明白为什么编译器在这里选择新的唯一 ptr 重载,据我所知,std::complex 没有名为 pointer 的公共类型。

【问题讨论】:

  • unique_ptr 放入initializer_list 是没有意义的,因为您只有const 对其成员的访问权限;你不能离开他们。
  • 我不是想将unique_ptr 放在initializer_list 中,而是想将指向unique_ptr 的底层类型的指针放入列表中

标签: c++ c++14 sfinae


【解决方案1】:

你只是缺少一个细节。

SFINAE 仅适用于直接上下文,不包括类中的模板参数。仅来自方法的模板参数。

解决这个问题的一种方法是在方法中使用一个额外的模板参数,默认为T

template <typename U = T, typename OwnedTypePtr = typename U::pointer>
MyVector (std::initializer_list<typename U::pointer> il)
{
    vector.reserve (il.size());
    for (auto* ptr : il)
        vector.emplace_back (ptr);
}

当我们以无效的方式使用U 时,SFINAE 将启动。

【讨论】:

  • 谢谢——完全忽略了这一点。虽然这乍一看很有效,但我现在遇到了 T=std::complex 的麻烦。 godbolt.org/z/Wo7qG1据此编辑了我的原帖
  • @PluginPenguin 啊,是的。我们不能在参数中使用OwnedTypePtr,因为这样可以扣除我们传入的内容,在这种情况下,模板参数的默认值将被忽略。如果我们用typename U::pointer 替换它,我们会得到正确的行为。这也是我通常使用非类型模板参数来做 SFINAE 的原因之一。如果我们想用不同的 SFINAE 重载几个模板构造函数,它甚至是必需的。不过有点冗长。
  • 啊啊,当然,我明白了……这完全有道理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-07
  • 2016-05-31
  • 2014-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多