上篇博客的解答与模板的应用

之前的一篇博客中已经提到过了, 使用模板的目的是提高效率, 可是会因为用户输入的不可预知性导致计划中的函数没有匹配到, 而是被模板函数接收, 所以我们的策略就是, 使用 SFINAE 这个 trick:

template<typename T>

void LogAndAdd(T &&name)

{

    LogAndAddImpl

    (std::forward<T>(name)

    , std::is_integral<T>());

}

可是问题来了, 传入的引用是一个左值, 所以推断出的 T 就是那个左值的引用类型(如果传入一个 int, 那么推断出的 T 就是 int&) 所以揭开传入参数的本来面目, 就要把引用去掉, 对于这个需求, 简单暴力的模板函数:

remove_reference<T>

简单暴力到看名字就知道这是什么了, 是这么用的:

template<typename T>

void LogAndAdd(T &&name)

{

    LogAndAddImpl(

        std::forward<T>(name)

        , std::is_integral<typename std::remove_reference<T>::type>()

    );

}

然而, 这又引出一个问题: is_integral<T>() 的值是在运行时才能判断的, 而使用模板的目的就是想要把在运行时处理的问题提前到编译时期, 所以解决的方式是使用 tags, 我个人觉得有点 switch case 的神韵:

//两种情况

template<typename T>

void LogAndAddImpl(T &&name, std::flase_type)

{

    auto now = chrono::system_clock::now();

    log(now, "logAndAdd");

    names.emplace(std::forward<T>(name));

}



std::string NameFromIdx(int idx, std::true_type)

{

    void LogAndAdd(NameFromIdx(idx));

}

好, LogAndAdd 问题已经基本被比较满意的解决了, 下面则是对于类 Person 构造函数的问题.
对于这个问题我们要使用一个 trick, 叫做 enable_if<conditon>:type, 大概意思就是, 只有在满足 conditon 的条件下 , 其中的 type 才会被产生:

class Person{

public:

    template<typename T

            , typename = 

                typename std::enable_if<condition>::type>

                explicit Person(T &&n);

    ...

};

enable_if 其实又是另一个 trick 的一个运用, 这个 trick 叫做 SFINAE, 在此也不细说.
然后问题来了, 这既然是一个 拷贝/移动 构造函数的模板, 那么怎么判断拷贝的是不是一个 Person 呢?
这里又有一个模板类, std::is_same<T, U>, 其中, std::is_same<T, U>::type 的类型是 bool, 用于表示 T 和 U 是否是同一类型. 这样, 问题就解决了......吗? 并不是, 还需要考虑到两个问题:
  1. Person, Person& 和 Person&& 可不是一个类型
  2. 是否有 const 和 volatile 的前缀又是一个问题
如此多的干扰, 总不能把这些情况的排列组合写一遍吧?! 幸好这里又有一个使参数返璞归真的模板类 std::decay<T>, 它的 type 就是 T 的本来面目. 剔除了干扰, 就方便我们的判断了:

class Person{

public:

    template<

        typename T

        , typename = typename std::enable_if<

            !!std::is_same<Person

                          , typename std::decay<T>::type

                          >::value

                     >::type

        >

    explicit Person(T &&n);

    ...

};

终于完成了! 我简直想要撒h......需求又变了, Person 出于某种原因, 需要一个子类: SpecialPerson.

class SpecialPerson : public Person{

public:

    SpecialPerson(const SpecialPerson &rhs)

        :Person(rhs)

    {...}

    

    SpecialPerson(SpecialPerson &&rhs)

        :Person(std::move(rhs))

    {...}

    ...

};

看起来倒是正确的, 可是, Person 和 SpecialPerson 可不是一样的, 即使是在 decay 之后(果然够 special), 直接放进去必然会出错. 那么这该如何是好呢?
所以说 C++11 大法好, 居然还有亲子鉴定模板类 std::is_base_of<T, U>, 用于判断 U 是否继承 T. std::is_base_of<T, U>::value 也是一个 bool 类型的值, 所以我们只要把之前的代码稍加修改:

class Person{

public:

    tempalte<

        typename T

        ,   typename = typename std::enable_if<

                        !std::is_base_of<Person

                                        , typename std::decay<T>::type

                                        >::value

                       >::type

    >

    explicit Person(T &&n);

    ...

};

哈哈哈! 终于完成了, 只是有一点点不爽: 中间又是 type 又是 value 的, 一不小心就乱了(我初学, 容易弄乱) 所以对于 C++11, 还是 C++14 大法更好:

//C++14

class Person{

public:

    tempalte<

        typename T

        , typename = std::enable_if_t<

                        !std::is_base_of<Person

                                        , std::decay_t<T>

                                    >::value

                     >

        >

        explicit Person(T &&n);

        ...

};

我突然发现边看书边做笔记的坏处, 就是容易被书牵着鼻子走, 我都不知道已经是第几遍说:"这样就完成了!" 结果呢? 尼玛居然还是没完!
为啥捏? 因为假如用户给咱们的模板函数传入一个既不是 Person, 也不是 SpecialPerson 还不是 整数的参数, 这个函数会如何处理呢? 哈, 当然是当作整数来处理了......这能行? 所以要再加一道锁链:

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))

    {...}

    ...

};

到此大功告成, 无论传进模板化构造函数的参数是左值还是右值, 都会得到最佳的处理, 无论是 有着 const 还是 volatile 都会被正确的处理, 传入的整数也不会被错误的被卷入模板. 我们仅用了区区这几行代码就一举处理了如此多的情况, 证明之前的讨论是值得的.
最后的最后, 其实还是有一个问题, 这个设计是正确的, 问题是 forward<T> 本身有缺陷
  1. forward 这个过程并不完美, 在以后细说.
  2. 错误信息不明确, 这个说明一下:
举个例子, 有个用户传进来一个 char16_t 的数组:

Person(u"Konrad Zuse"); //character of type fucking const char16_t

模板遇到这个奇葩就会给出一个错误信息......嗯, 最多大概能有 160 行, 中间路过的层数越多, 给的错误信息就越多.
咋办呢? 既然要检查, 就别让错误走远, 一开始就来个断言, 并自己给一个说人话的错误信息, 此外也把运行时的错误检查提前到编译期, 提升性能.

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>>::value

        >

    >

    explicit Person(T &&n)

        :_name(forward<T>(n))

    {

        static_assert(

            std::is_constrctible<std::string, T>::value

            , "Parameter n cannot be used to construct a std::string"

        );

        ...

    }

    ...

};

 

你可能感兴趣的:(博客)