Skip to content

Latest commit

 

History

History
162 lines (132 loc) · 3.7 KB

File metadata and controls

162 lines (132 loc) · 3.7 KB

条款27:熟悉依万能引用类型进行重载的替代方案

舍弃重载

logAndAdd 改为两个函数 logAndAddNamelogAndAddNameIdx。但是这样就无法适配 Person 类的构造函数。

传递 const T& 类型的形参

回归C++98,使用传递左值常量引用类型来代替传递万能引用类型。不过这会带来效率损失。

传值

将传递的形参从引用类型替换成值类型:

class Person {
public:
    explicit Person(std::string n)
    : name(std::move(n)) {}
    explicit Person(int idx)
    : name(nameFromIdx(idx)) {}
    ...
private:
    std::string name;
};

这样不会带来效率的损失,也能让重载决议符合期望。

标签分派

如果我们既不想放弃虫子啊,二不想放弃万能引用,哪有如何避免依万能引用类型进行重载呢:

先回顾下先前的代码:

std::multiset<std::string> names;

template<typename T>
void logAndAdd(T&& name)
{
    auto now = std::chrono::system_clock::now();
    log(now, "logAndAdd");
    names.emplace(std::forward<T>(name));
}

我们将它改为:

template<typename T>
void logAndAdd(T&& name)
{
    logAndAddImpl(std::forward<T>(name),
                  std::is_integral<std::remove_reference_t<T>());
}

template <typename T>
void logAndAddImpl(T&& name, std::false_type)
{
    auto now = std::chrono::system_clock::now();
    log(now, "logAndAdd");
    names.emplace(std::forward<T>(name));
}

void logAndAddImpl(int idx, std::true_type)
{
    logAndAdd(nameFromIdx(idx));
}

std::true_typestd::false_type 都是用来参与重载决议的标签。

对接收万能引用的模板施加限制

上面的实现方法依然在部分场景(如完美转发构造函数)中无法达到预期效果。这样就需要 std::enable_if 将含有万能引用部分的函数模板被允许采用的条件砍掉一部分。

在我们讨论的情况下,仅在传递给完美转发构造函数的类型不是 Person 时才会启用该模板函数,这样就可以让复制构造函数等函数正常执行。

修改后的代码如下:

class Person {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_same_v<Person, std::decay_t<T>>
        >
    >
    explicit Person(T&& n);
    ...
};

为了实现继承,需要修改如下:

class Person {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_base_of<Person, std::decay_t<T>>::value
        >
    >
    explicit Person(T&& n);
    ...
};

最后,还需要加入索引功能:

class Person {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_base_of<Person, std::decay_t<T>>::value
            &&
            !std::is_integral<std::remove_reference_t<T>>::value
        >
    >
    explicit Person(T&& n)
    : name(std::forward<T>(n))
    {...}

    explicit Person(int idx)
    : name(nameFromIdx(idx))
    {...}
    ...
private:
    std::string name;
};

为了保证传入完美转发构造函数的参数类型可以转换为 std::string ,需要添加静态断言,因为模板元的报错信息很难看懂:

class Person {
public:
    template<
        typename T,
        typename = std::enable_if_t<
            !std::is_base_of<Person, std::decay_t<T>>::value
            &&
            !std::is_integral<std::remove_reference_t<T>>::value
        >
    >
    explicit Person(T&& n)
    : name(std::forward<T>(n))
    {
        static_assert(
            std::is_constructible<std::string, T>::value,
            "Parameter n can't be used to construct a std::string"
        );
        ...
    }
    ...
};