第6章 移动语义和enable_if:6.4 使用enable_if<>

6.4 Using enable_if<>

6.4 使用enable_if<>

 

We can use enable_if<> to solve our problem with the constructor template introduced in Section 6.2 on page 95.

通过使用enable_if<>可以解决在第95页6.2节中讲到的构造函数模板问题。

 

The problem we have to solve is to disable the declaration of the template constructor

我们要解决的问题是:

template<typename STR>
Person(STR&& n);

if the passed argument STR has the right type (i.e., is a std::string or a type convertible to std::string).

当传递的STR参数类型不正确时(如不是std::string类型或不可转换为std::string的类型),则禁用构造函数模板。

 

For this, we use another standard type trait, std::is_convertible<FROM,TO>. With C++17, the corresponding declaration looks as follows:

为此,需要使用别一个标准库类型萃取,std::is_convertible<FROM,TO>。在C++17中,相应的构造函数模板的声明如下:

template<typename STR,
         typename = std::enable_if_t<std::is_convertible_v<STR, std::string>>>
Person(STR&& n);  

 

 If type STR is convertible to type std::string, the whole declaration expands to

如果STR可以转换为std::string类型,整个声明会扩展成

template<typename STR, typename = void>
Person(STR&& n);

 

If type STR is not convertible to type std::string, the whole function template is ignored.

否则,这个函数模板将会被忽略。

 

Again, we can define our own name for the constraint by using an alias template:

这样,我们可以使用别名模板来给约束条件起一个自定义的名字:

template<typename T>
using EnableIfString = std::enable_if_t<std::is_convertible_v<T, std::string>>;

…

template<typename STR, typename = EnableIfString<STR>>
Person(STR&& n);

 Thus, the whole class Person should look as follows:

因此,整个Person类如下:

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>

template<typename T>
using EnableIfString = std::enable_if_t<std::is_convertible_v<T, std::string>>;

class Person
{
private:
    std::string name;
public:
    // generic constructor for passed initial name:
    template<typename STR, typename = EnableIfString<STR>>
    explicit Person(STR && n) : name(std::forward<STR>(n)) {
        std::cout << "TMPL-CONSTR for ‘" << name << "‘\n";
    }

    // copy and move constructor:
    Person(Person const& p) : name(p.name) {
        std::cout << "COPY-CONSTR Person ‘" << name << "‘\n";
    }

    Person(Person&& p) noexcept : name(std::move(p.name)) {
        std::cout << "MOVE-CONSTR Person ‘" << name << "‘\n";
    }
};

Now, all calls behave as expected:

现在,所有的调用都符合预期:

#include "specialmemtmpl3.hpp"
int main()
{
    std::string s = "sname";
    Person p1(s); // init with string object => calls TMPL-CONSTR
    Person p2("tmp"); // init with string literal => calls TMPL-CONSTR

    Person p3(p1); // OK => calls COPY-CONSTR
    Person p4(std::move(p1)); // OK => calls MOVE-CONST
}

Note again that in C++14, we have to declare the alias template as follows, because the _v version is not defined for type traits that yield a value:

此外,要注意在C++14中,由于并没有定义一个可以获取值(即带_v的版本)的类型萃取。因此,我们必须像如下声明别名模板:

template<typename T>
using EnableIfString = std::enable_if_t<std::is_convertible<T, std::string>::value>;

And in C++11, we have to declare the special member template as follows, because as written the _t version is not defined for type traits that yield a type:

而在C++11中,由于没有定义一个可以可以获取类型(即带_t)的类型萃取。因此,我们必须像如下声明成员函数模板:

template<typename T>
using EnableIfString = typename std::enable_if<std::is_convertible<T,std::string>::value>::type;

 

But that’s all hidden now in the definition of EnableIfString<>.

但是通过定义EnableIfString<>,这些复杂的语法都被隐藏了。

 

Note also that there is an alternative to using std::is_convertible<> because it requires that the types are implicitly convertible. By using std::is_constructible<>, we also allow explicit conversions to be used for the initialization. However, the order of the arguments is the opposite is this case:

还请注意,除了使用要求类型之间可以隐式转换的std::is_convertible<>之外,还可以使用std::is_constructible<>,因为它允许在初始化使用显式类型转换。但是在这种情况下,参数的顺序和std::is_ convertible <>是相反的:

template<typename T>
using EnableIfString = std::enable_if_t<std::is_constructible_v<std::string, T>>;

See Section D.3.2 on page 719 for details about std::is_constructible<> and Section D.3.3 on page 727 for details about std::is_convertible<>. See Section D.6 on page 734 for details and examples to apply enable_if<> on variadic templates.

第719页的D.3.2节详细讨论了std::is_constructible<>,第727页的D.3.3讨论了std::is_convertible<>的使用细节。关于enable_if在可变参数模板中的使用例子,可参阅第734页的D.6节。

 

Disabling Special Member Functions

禁用特殊成员函数

 

Note that normally we can’t use enable_if<> to disable the predefined copy/move constructors and/or assignment operators. The reason is that member function templates never count as special member functions and are ignored when, for example, a copy constructor is needed. Thus, with this declaration:

通常我们不能使用enable_if<>来禁用预定义的拷贝/移动构造函数以及赋值构造函数。因为成员函数模板并不能算是特殊成员函数。而且当需要进行拷贝构造时,成员函数模板会被忽略掉(译注:依然会生成默认构造函数,且优先于成员模板被匹配)。因此,按照下面的声明

class C {
public:
    template<typename T>
    C (T const&) {
        std::cout << "tmpl copy constructor\n";
    }
    …
};

the predefined copy constructor is still used, when a copy of a C is requested:

当需要拷贝C的地方,依然会使用预定义的拷贝构造函数:

C x;
C y{x}; // 仍然使用的是默认拷贝构造函数 (而不是成员模板)

(There is really no way to use the member template because there is no way to specify or deduce its template parameter T.)

(实例上没有办法使用成员模板,因为并没有办法指定或推导其模板参数T)

 

Deleting the predefined copy constructor is no solution, because then the trial to copy a C results in an error.

删除预定拷贝构造函数也不行,因为这样当在尝试拷贝C时会导致错误。

 

There is a tricky solution, though: We can declare a copy constructor for const volatile arguments and mark it “deleted” (i.e., define it with = delete). Doing so prevents another copy constructor from being implicitly declared. With that in place, we can define a constructor template that will be preferred over the (deleted) copy constructor for nonvolatile types:

但是这里有一个技巧性的解决方案:我们可以声明一个接受const volatile参数的构造函数并将其标为deleted(也就是定义为=delete)。这样做就会阻止隐式生成默认构造函数。在此基础上,我们可以定义的一个构造函数模板,对于非volatile类型的参数,成员模板会被优先匹配(相较于己删除的copy构造函数):

class C
{
public:
    …
    // user-define the predefined copy constructor as deleted
    // (with conversion to volatile to enable better matches)
    C(C const volatile&) = delete;

    // implement copy constructor template with better match:
    template<typename T>
    C (T const&) {
        std::cout << "tmpl copy constructor\n";
    }
    …
};

Now the template constructors are used even for “normal” copying:

现在,即使是常规的拷贝,也是使用模板构造函数:

C x;
C y{x}; // 使用成员模板

In such a template constructor we can then apply additional constraints with enable_if<>. For example, to prevent being able to copy objects of a class template C<> if the template parameter is an integral type, we can implement the following:

于是就可以给这个模板构造函数施加enable_if<>限制。例如,可以禁止对通过int类型参数实例化出来的C<>实例进行拷贝,实现如下:

class C
{
public:
   ...

   // user-define the predefined copy constructor as deleted
   // (with conversion to volatile to enable better matches)
   C(C const volatile&) = delete;

   // if T is no integral type, provide copy constructor template with better match:
   template<typename U,
            typename = std::enable_if_t<!std::is_integral<U>::value>>
    C(C<U> const&) {
        ...
    }
   ...

};

【编程实验】完美转发构造函的匹配

#include <iostream>

class Test
{
public:
    Test() = default;
    Test(const Test&) = delete;

    template<typename T>
    Test(const T&) {
        std::cout << "impl Test(const T&)"<< std::endl;
    }
};

class Demo
{
public:
    Demo() = default;

    //技巧:由于定义了const volatile版本(并标为delete)的拷贝构造函数
    //编译器不再隐式提供默认拷贝构造函数
    Demo(const volatile Demo&) = delete;

    template<typename T>
    Demo(const T&)
    {
        std::cout << "impl Demo(const T&)" << std::endl;
    }
};

int main()
{
    Test t;
    //Test t1(t); //由于拷贝函数被声明为delete,就说明该类不允许拷贝。
                  //此时需要成员函数可以生成一个对应的拷贝构造函数。
                  //但是Test(const Test&) = delete;这个普通的会被优先匹配。
                  //因此,无法编译通过。
    Demo d;
    Demo d1(d);  //OK。当尝试拷贝d时,由于找不到默认拷贝构造函数Demo(const Demo&)
                 //而且该函数也未声明为delete,说明是允许拷贝的。因此会尝试匹配
                 //成员模板,这里会找到一个匹配,因此编译通过。

    return 0;
}
/*输出结果
impl Demo(const T&)
*/

 

第6章 移动语义和enable_if:6.4 使用enable_if<>

上一篇:IOS中 类扩展 xib


下一篇:Android:通知(Notification)版本适配