它能处理不同模板类型的特化,因为它可以在完全不同的代码中,选取相应的片段,依据这些片段的类型对模板进行特化

比如我们有一个简单的类,它的成员函数 add ,支持对 U 类型值与 T 类型值的加法

template <typename T> 
class addable { 
    T val; 
public: 
    addable(T v) : val{v} {} 
    
    template <typename U> 
    T add(U x) const { 
        return val + x; 
    }
};

假设类型 T 是 std::vector<something> ,而类型 Uint。这里就有问题了,为整个 vector 添加整数是为 了什么呢?应该是对 vector 中的每个元素加上一个整型数。实现这个功能就需要在循环中进行

template <typename U> 
T add(U x) { 
    auto copy (val); // Get a copy of the vector member 
    for (auto &n : copy) { 
        n += x; 
    }
    return copy; 
}

把两种情况结合在一起:

template <typename U> 
T add(U x) const{ 
    if constexpr(std::is_same<T, std::vector<U>>::value){ 
        auto copy(val); 
        for (auto &n : copy){ 
            n += x; 
        }
        return copy; 
    } else { 
        return val + x; 
    } 
}

使用 constexpr-if 的代码在编译完成后,程序的这一部分其实就不会有分支存在。有种方式类似于 constexpr-if,那就是 #if-#else 的预编译方式进行宏替换,不过这种方式在代码的构成方面不是那么优雅。

在一个 constexpr-if-else 代码块中,可以有多个条件(注意:ab 也可以依赖于模板参数,并不需要其为编译时常量):

if constexpr(a){ 
    // do something } 
else if constexpr(b){ 
    // do something else 
} else { 
    // do something completely different 
}

之前的写法:

template <typename T> 
class addable{ 
    T val; 
public: 
    addable(T v):val{v}{} 
    
    template <typename U> 
    std::enable_if_t<!std::is_same<T, std::vector<U>>::value, T> 
    add(U x) const { 
        return val + x; 
    }
    
    template <typename U> 
    std::enable_if_t<std::is_same<T, std::vector<U>>::value, std::vector<U>> 
    add (U x) const{ 
        auto copy(val); 
        for (auto &n: copy){ 
            n += x; 
        }
        return copy; 
    } 
};

当编译器看到具有相同名称的不同模板函数并不得不选择其中一个时,一个重要的原则就起作用了:替换失败不是错误(SFINAE, Substitution Failure is not An Error)。这个例子中,就意味着如果函数的返回值来源一个错误的模板表示,无法推断得出,这时编译器不会将这种情况视为错误(和 std::enable_if 中的条件为 false 时的状态一样)。这样编译器就会去找函数的另外的实现。

测试:

int main()
{    
    auto t1 = addable<int> {1}.add(2); // is 3
    cout << t1 << endl;
    auto t2 = addable<float> {1.f}.add(2); // is 3.0 
    cout << t2 << endl;
    auto t3 = addable<std::string> {"aa"}.add("bb"); // is "aabb"
    cout << t3 << endl; 
    std::vector<int> v{1, 2, 3}; 
    auto t4 = addable<std::vector<int>> {v}.add(10); // is std::vector<int> {11, 12, 13} 
    for_each(t4.begin(), t4.end(), [](const auto &i){cout << i << " ";});
    cout << endl;
    std::vector<std::string> sv{"a", "b", "c"}; 
    auto t5 = addable<std::vector<std::string>> {sv}.add(std::string{"z"}); // is {"az", "bz", "cz"} 
    for_each(t5.begin(), t5.end(), [](const auto &i){cout << i << " ";});
    cout << endl;
    
    return 0;
}

输出:

3
3
aabb
11 12 13
az bz cz