You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中无分支地根据条件调用两个模板成员函数之一

问题描述

现有一个包含两个成员模板函数fun1()、fun2()的类Foo,希望根据类的const bool成员willCallFun1的值决定调用其中一个函数。当前实现的fun()函数每次调用都会执行if-else分支判断,但willCallFun1在Foo对象构造时就已确定,因此希望消除该分支。

若fun1、fun2为非模板函数,可在类中添加函数指针或std::function成员并在构造时绑定目标函数,但针对模板函数暂无解决办法,寻求解决方案。

原始代码如下:

class Foo {
public:

  const bool willCallFun1;

  Foo(bool b) : willCallFun1{b}{
  }

  template<typename T>
  void fun1(T t) {
    std::cout << "fun1 "<< t << "\n";
  }

  template<typename T>
  void fun2(T t) {
    std::cout << "fun2 "<< t << "\n";
  }

  template<typename T>
  void fun(T t){
    if (willCallFun1) fun1<T>(t);
    else fun2<T>(t);
  }

};

int main() {
    Foo f1{true};
    f1.fun(123);
    f1.fun("hi");

    Foo f2{false};
    f2.fun(456);
    f2.fun("hello");
}

解决方案

方案一:将willCallFun1转为类模板参数(C++17+)

把willCallFun1从类成员变量改成类模板的非类型参数,配合if constexpr在编译期直接剔除无用分支,完全消除运行时判断。

修改后的代码:

template<bool WillCallFun1>
class Foo {
public:
  template<typename T>
  void fun1(T t) {
    std::cout << "fun1 "<< t << "\n";
  }

  template<typename T>
  void fun2(T t) {
    std::cout << "fun2 "<< t << "\n";
  }

  template<typename T>
  void fun(T t){
    if constexpr (WillCallFun1) {
      fun1<T>(t);
    } else {
      fun2<T>(t);
    }
  }
};

int main() {
    Foo<true> f1;
    f1.fun(123);
    f1.fun("hi");

    Foo<false> f2;
    f2.fun(456);
    f2.fun("hello");
}

优缺点:实现最简单,无任何运行时开销;但不同WillCallFun1值对应不同的类类型,无法直接放到同一容器中(需用多态或变体包装)。

方案二:标签分发(Tag Dispatch,C++11+)

如果不想修改类的非模板特性,可通过标签分发在编译期选择对应函数重载。核心是让willCallFun1成为编译期常量,再用std::conditional_t选择标签类型,触发对应重载。

修改后的代码:

#include <type_traits>
#include <iostream>

class Foo {
public:
  constexpr bool willCallFun1;

  constexpr Foo(bool b) : willCallFun1{b}{
  }

  template<typename T>
  void fun1(T t) {
    std::cout << "fun1 "<< t << "\n";
  }

  template<typename T>
  void fun2(T t) {
    std::cout << "fun2 "<< t << "\n";
  }

private:
  // 定义用于分发的标签类型
  struct Fun1Tag {};
  struct Fun2Tag {};

  // 针对不同标签的实现重载
  template<typename T>
  void fun_impl(T t, Fun1Tag) {
    fun1<T>(t);
  }

  template<typename T>
  void fun_impl(T t, Fun2Tag) {
    fun2<T>(t);
  }

public:
  template<typename T>
  void fun(T t){
    // 编译期选择标签,触发对应重载
    fun_impl(t, std::conditional_t<willCallFun1, Fun1Tag, Fun2Tag>{});
  }
};

int main() {
    constexpr Foo f1{true};
    f1.fun(123);
    f1.fun("hi");

    constexpr Foo f2{false};
    f2.fun(456);
    f2.fun("hello");
}

优缺点:保留原类的非模板特性,对象类型统一;但要求willCallFun1是constexpr(编译期可知),如果willCallFun1只能在运行时确定,此方法不适用。

方案三:继承+CRTP(奇异递归模板模式,C++11+)

通过CRTP将不同的行为逻辑封装到基类中,派生类继承对应基类来获得目标行为,既消除运行时分支,又可通过工厂函数统一对外接口。

修改后的代码:

#include <iostream>

// 基类模板,封装fun的选择逻辑
template<bool WillCallFun1, typename Derived>
class FooBase {
protected:
  template<typename T>
  void fun(T t) {
    if constexpr (WillCallFun1) {
      static_cast<Derived*>(this)->template fun1<T>(t);
    } else {
      static_cast<Derived*>(this)->template fun2<T>(t);
    }
  }
};

// 对应willCallFun1=true的派生类
class FooTrue : public FooBase<true, FooTrue> {
public:
  template<typename T>
  void fun1(T t) {
    std::cout << "fun1 "<< t << "\n";
  }

  template<typename T>
  void fun2(T t) {
    std::cout << "fun2 "<< t << "\n";
  }

  // 暴露基类的fun函数
  using FooBase<true, FooTrue>::fun;
};

// 对应willCallFun1=false的派生类
class FooFalse : public FooBase<false, FooFalse> {
public:
  template<typename T>
  void fun1(T t) {
    std::cout << "fun1 "<< t << "\n";
  }

  template<typename T>
  void fun2(T t) {
    std::cout << "fun2 "<< t << "\n";
  }

  using FooBase<false, FooFalse>::fun;
};

// 工厂函数,统一创建对象接口
template<bool B>
auto makeFoo() {
  if constexpr (B) return FooTrue{};
  else return FooFalse{};
}

int main() {
    auto f1 = makeFoo<true>();
    f1.fun(123);
    f1.fun("hi");

    auto f2 = makeFoo<false>();
    f2.fun(456);
    f2.fun("hello");
}

优缺点:可通过工厂函数提供统一的创建接口,同时消除运行时分支;缺点是需要定义多个派生类,代码量稍大。


内容的提问来源于stack exchange,提问作者Wayne Tseng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 21:05:53