constexpr与std::is_same_v碰撞会产生什么火花?

时间:2024-03-30 10:08:11

1. 只编译会用到的if分支

示例代码一中,checkType_v1checkType_v2两个函数的区别就是if的条件里一个加了constexpr一个没加,加与不加从结果来看都一样,那在编译时和运行时各有什么区别呢?

示例代码一test_01.cpp

// g++  test_01.cpp  -std=c++17

#include <iostream>
#include <type_traits>

template<class T>
void checkType_v1(T){
    if constexpr (std::is_same_v<T, int>){
        std::cout << "Input is an int.\n";
    }
    else if constexpr (std::is_same_v<T, float>){
        std::cout << "Input is a float.\n";
    }
    else if constexpr (std::is_same_v<T, double>){
        std::cout << "Input is a double.\n";
    }
    else{
        std::cout << "Unsupported type!\n";
    }
}

template<class T>
void checkType_v2(T){
    if (std::is_same_v<T, int>){
        std::cout << "Input is an int.\n";
    }
    else if (std::is_same_v<T, float>){
        std::cout << "Input is a float.\n";
    }
    else if (std::is_same_v<T, double>){
        std::cout << "Input is a double.\n";
    }
    else{
        std::cout << "Unsupported type!\n";
    }
}

int main(){
    checkType_v1(4);   // Input is an int.
    checkType_v1(4.f); // Input is a float.
    checkType_v1(4.0); // Input is a double.
    checkType_v1('A'); // Unsupported type!

    checkType_v2(4);   // Input is an int.
    checkType_v2(4.f); // Input is a float.
    checkType_v2(4.0); // Input is a double.
    checkType_v2('A'); // Unsupported type!    
}

【来自C++大咖吴老师的解答】如果你要调用一个只接受某种类型的函数,那就必须用 if constexpr。此外,用 if constexpr 条件判断是编译是做出的,没用到的分支完全不会在某个类型的特化里产生二进制代码。

举例说明,也就是说针对checkType_v1的版本,假设调用它的时候传入的是int类型的参数,那么编译的二进制文件中只有代码里的第一个分支的实现。而checkType_v2的版本的二进制文件中是有整个函数的实现。

这么做的目的有如下三个(其中第三个最重要):

  1. 降低运行时的判断时间;
  2. 减少编译后二进制文件的大小;
  3. 【来自C++大咖吴老师的解答】但最重要的是,有些情况下你对特定类型要走的分支在其他类型的情况下可能完全编译不通过!比如,vector可以reservedeque不可以;deque可以push_frontvector不可以。

2. 验证上面第3点

示例代码二test_02.cpp

// g++  test_02.cpp  -std=c++17
#include <iostream>
#include <type_traits>
#include <typeinfo>
#include <vector>
#include <set>
#include <deque>

template<class Container>
void expandContainer(Container& container, int val){
    if constexpr (std::is_same_v<Container, std::vector<int>>){
        container.push_back(val);
        for(const auto&it: container){
            std::cout << it << ", ";
        }
        std::cout << "vector number added.\n";
    }
    else if constexpr (std::is_same_v<Container, std::deque<int>>){
        container.push_front(val);
        for(const auto&it: container){
            std::cout << it << ", ";
        }
        std::cout << "deque number added.\n";
    }
    else{
        container.insert(val);
        for(const auto&it: container){
            std::cout << it << ", ";
        }
        std::cout << "Other container number added.\n";
    }
}

int main(){
    std::vector<int> vec{1,2,3};
    expandContainer(vec,100);     // 1, 2, 3, 100, vector number added.

    std::deque<int> deq{4,5,6};
    expandContainer(deq, 200);    // 200, 4, 5, 6, deque number added.

    std::set<int> aset{7,8,9};
    expandContainer(aset, 300);   // 7, 8, 9, 300, Other container number added.

假如去掉constexpr,如示例代码三test_03.cpp

#include <iostream>
#include <type_traits>
#include <typeinfo>
#include <vector>
#include <set>
#include <deque>

template<class Container>
void expandContainer(Container& container, int val){
    if constexpr (std::is_same_v<Container, std::vector<int>>){
        container.push_back(val);
        for(const auto&it: container){
            std::cout << it << ", ";
        }
        std::cout << "vector number added.\n";
    }
    else if /*constexpr*/ (std::is_same_v<Container, std::deque<int>>){
        container.push_front(val);
        for(const auto&it: container){
            std::cout << it << ", ";
        }
        std::cout << "deque number added.\n";
    }
    else{
        container.insert(val);
        for(const auto&it: container){
            std::cout << it << ", ";
        }
        std::cout << "Other container number added.\n";
    }
}

int main(){
    std::vector<int> vec{1,2,3};
    expandContainer(vec,100);     // 1, 2, 3, 100, vector number added.

    //std::deque<int> deq{4,5,6};
    //expandContainer(deq, 200);    // 200, 4, 5, 6, deque number added.

    std::set<int> aset{7,8,9};
    expandContainer(aset, 300);   // 7, 8, 9, 300, Other container number added.
}

此时编译会有如下报错:
在这里插入图片描述
走哪个分支如果在编译时无法确定那么就要保障运行时所有的分支都可以走,很显然else if的分支中有push_front操作,但std::set不支持,所以编译会报错。
因此,如果传入的参数只能走特定的分支,只能在编译时就限制住走的路径,即使用if constexprelse if constexpr