【问题标题】:Call function based on template argument type基于模板参数类型的调用函数
【发布时间】:2025-11-30 19:25:02
【问题描述】:

有两个“C”函数:

void fooA(const char*);
void fooW(const wchar_t*);

然后是一个包装模板函数:

template<typename _TChar>
void foo(const _TChar* str)
{
     // call fooA or fooB based on actual type of _TChar
     // std::conditional .. ?
         // fooA(str); 
         // fooW(str);
}

如果调用者调用foo("Abc"),这个模板函数应该在编译时调用fooA。类似地,foo(L"Abc") 应该最后调用fooW

我该怎么做?我曾想过使用std::conditional,但没有成功。

我不能让fooAfooB 重载,因为它们是C 函数。

【问题讨论】:

    标签: c++ c++11 visual-c++ c++14


    【解决方案1】:

    您可以将所有您的wchar_t 版本放在类模板中,例如overloads 和它们的char 对应部分,如下所示:

    template<typename WideCharVersion> 
    struct overloads
    {
        void foo(wchar_t const * arg)
        {
           FooW(arg);
        }
        //more wchar_t functions
    };
    
    template<> 
    struct overloads<std::false_type>
    {
        void foo(char const * arg)
        {
           FooA(arg);
        }
        //more char functions
    };
    
    //a friendly alias!
    template<typename T>
    using is_wide_char = typename std::is_same<whar_t, T>::type;
    

    然后您可以将它们用作:

    template<typename _TChar>
    void foo(const _TChar* str)
    {
        overloads<is_wide_char<_TChar>>::foo(str);
    }
    

    表达 SFINAE 让一切变得简单!

    另一种方法是使用Expression SFINAE,它不需要您编写类似overloads 的任何内容,并且可以用更少的代码完成相同的工作:

    template<typename _TChar>
    void foo(const _TChar* str)
    {
        invokeOne(fooA, fooW, str);
    }
    

    然后您可以将invokeOne 实现为:

     template<typename F1, typename F2, typename ... Args>
     auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f1(args...))
     {
         return f1(args...);
     }
    
     template<typename F1, typename F2, typename ... Args>
     auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f2(args...))
     {
         return f2(args...);
     }
    

    看看online demo

    在这种方法中,您不必将重载添加到overloads 类模板 到其特化中。相反,您只需将它们作为参数传递给 invokeOne,它会为您调用正确的重载。

    希望对您有所帮助。

    【讨论】:

    • 这在哪些方面比乐高和IanM_Matrix1的解决方案更好?
    • @tamas.kenez:第一种方法类似。但是,SFINAE 方法与他们的方法不同,后者需要更少的代码来完成工作。
    【解决方案2】:

    然后重载另一个函数。我假设foo 做了更多的工作并且需要成为一个模板。然后调用foo_forward_call,定义如下:

    void foo_forward_call(char const* ptr) {
        FooA(ptr);
    }
    
    void foo_forward_call(wchar_t const* ptr) {
        FooW(ptr);
    }
    

    在通话现场:

    template<typename _TChar>
    void foo(const _TChar* str)
    {
        foo_forward_call(str);
    }
    

    在 C++1z 中你将能够使用 constexpr if,但老实说,我认为重载的解决方案仍然更具可读性。

    template<typename _TChar>
    void foo(const _TChar* str)
    {
        if constexpr(std::is_same<_TChar, char>::value) {
            FooA(str);
        } else {
            FooW(str);
        }
    }
    

    或者,您可以使用Boost.Hanaoverload

    template<typename _TChar>
    void foo(const _TChar* str)
    {
        hana::overload(fooA, fooW)(str);
    }
    

    demo


    顺便说一句:您应该避免在程序中使用下划线大写字母名称。它们保留用于任何用途(即宏)的实现,并可能导致令人讨厌的名称冲突。

    【讨论】:

      【解决方案3】:

      这对于模板来说似乎是一件很奇怪的事情。我建议改用普通重载:

      void foo(const char* p) { fooA(p); }
      void foo(const wchar_t* p) { fooW(p); }
      

      如果你坚持使用模板,那么你可以这样做:

      template <typename T>
      void foo(const T* p)
      {
          // Declare functions here so that calling fooW with const char*
          // and 'calling' fooA with const wchar_t* would not cause compile error.
          void fooA(const T*);
          void fooW(const T*);
      
          if (std::is_same<char, T>::value)
              fooA(p);
          else
              fooW(p);
      }
      

      【讨论】:

      • 链接器将尝试解析 both 分支if-else,其中一个会导致链接器错误。如果TcharfooW 将导致链接器错误,否则fooA
      • 是的,但并非总是如此。这适用于 gcc 和 clang,不适用于 vs。要为 vs 解决这个问题,应该定义空重载,但是为什么要为所有这些模板的东西烦恼,只需使用普通重载。
      【解决方案4】:

      我喜欢解决一般问题。所以让我们设计一种机制来重载东西。

      overload_t&lt;...&gt; 采用... 中的一组可调用对象,并生成一个对象,该对象使用标准重载分辨率在它们之间进行选择,通过继承operator()

      template<class...Fs>
      struct overload_t;
      // the case where we have a function object:
      template<class F>
      struct overload_t<F>:F{
        overload_t(F f):F(std::move(f)){}
        using F::operator();
        // boilerplate to ensure these are enabled if possible:
        overload_t(overload_t&&)=default;
        overload_t(overload_t const&)=default;
        overload_t& operator=(overload_t&&)=default;
        overload_t& operator=(overload_t const&)=default;
      };
      // we cannot inherit from a function pointer.  So
      // store one, and write an `operator()` that forwards to it:
      template<class R, class...Args>
      struct overload_t<R(*)(Args...)>{
        using F=R(*)(Args...);
        F f;
        overload_t(F fin):f(fin){}
        R operator()(Args...args)const{
          return f(std::forward<Args>(args)...);
        }
        overload_t(overload_t&&)=default;
        overload_t(overload_t const&)=default;
        overload_t& operator=(overload_t&&)=default;
        overload_t& operator=(overload_t const&)=default;
      };
      // the case where we have more than type to overload.
      // recursively inherit from the one-arg and the rest-of-arg
      // and using operator() to bring both of their () into equal standing:
      template<class F0, class...Fs>
      struct overload_t<F0,Fs...>:
        overload_t<F0>,
        overload_t<Fs...>
      {
        using overload_t<F0>::operator();
        using overload_t<Fs...>::operator();
        overload_t(F0 f0, Fs...fs):
          overload_t<F0>(std::move(f0)),
          overload_t<Fs...>(std::move(fs)...)
        {}
        overload_t(overload_t&&)=default;
        overload_t(overload_t const&)=default;
        overload_t& operator=(overload_t&&)=default;
        overload_t& operator=(overload_t const&)=default;
      };
      // a helper function to create an overload set without
      // having to specify types.  Will be obsolete in C++17:
      template<class...Fs>
      overload_t<Fs...> overload(Fs...fs){ return {std::move(fs)...};}
      

      现在要生成多个重载的单个对象,请执行以下操作:

      overload(FooA,FooW)( str );
      

      并且str 将根据通常的重载解决规则被分派到其中一个。这在其他地方很有用,这就是值得编写的原因,并且使用时的代码是自我记录的。

      Live example(哇,第一次写对了!)

      可以对上述overload_t 进行多项改进。

      • 在构造期间和辅助函数中完美转发fs。

      • 平衡二叉树继承而不是线性继承(重要的是完成了多个重载)。这可能会对运行时和编译时性能产生影响,尤其是对于大量函数。

      • 内省传入的Fs;如果它们是overload_t,则平衡组合树。

      • 在 C++17 中,func&lt;auto&gt; 模板接受一个函数指针并返回一个无状态调用它的函数对象。编译器相对擅长省略函数指针,但是当没有可能的运行时状态可以改变它们时,它们会更好。

      • 决定为overload_t&lt;&gt; 做什么。目前无法编译;也许它应该只是一个空的struct {},甚至是一个带有不可调用operator() 的结构。

      • 检查现有库,例如 boost::hana::overload,看看有什么不同。

      • 公开提取将调用哪些重载的能力,可能通过static tag_t&lt;F&gt; which_overload_helper( Args... ) const 方法和template&lt;class...Args&gt; using which_overload = typename decltype( which_overload_helper( std::declval&lt;Args&gt;()... ) )::type;

      • 当一些传入的Fss 有/没有const operator() 时,正确选择重载。函数指针在operator() 上应该有constvolatile,还是两者都没有?全部4个? &amp;&amp;&amp; 重载怎么样?

      【讨论】:

        【解决方案5】:

        如果有更多的目标函数或者重载不是您试图解决的唯一问题,那么使用模板来完成这项工作可能是值得的。

        不过,在这种情况下,只需编写您的重载:

        void foo(const char* x) { fooA(x); }
        void foo(const wchar_t* x) { fooW(x); }
        

        【讨论】:

        • 这就是我最终所做的。但是对一项功能的任何更改也需要对另一项进行更改。
        【解决方案6】:

        你有几个选择。

        1. 使用明确专门化的帮助器struct

          template <typename>
          struct helper;
          
          template<>
          struct helper<char>
          {
              void operator()(const char* x){ FooA(x); }
          };
          
          template<>
          struct helper<wchar_t>
          {
              void operator()(const wchar_t* x){ FooW(x); }
          };
          
          template <typename _TChar>
          void foo(const _TChar* str)
          {
              helper<_TChar>{}(str);
          }
          
        2. 使用“静态 if”实现(例如 boost::hana::eval_ifmy own):

          template <typename _TChar>
          void foo(const _TChar* str)
          {
              vrm::core::static_if(std::is_same<_TChar, char>{})
                  .then([](const auto* x_str){ FooA(x_str); })
                  .else_([](const auto* x_str){ FooW(x_str); })(str);
          }
          
        3. 使用辅助重载函数:

          void helper(const char* x) { FooA(x); }
          void helper(const wchar_t* x) { FooW(x); }
          
          template <typename _TChar>
          void foo(const _TChar* str)
          {
              helper(str);
          }
          

        【讨论】:

        • 我认为.then(FooA).else_(FooW) 应该也可以吗?如果没有,图书馆的设计者也应该让它工作!
        • @Nawaz:它更复杂 - 每个分支都需要是一个模板,只有在条件匹配时才会被实例化。如果不是模板,编译将失败。 I discussed this in my CppCon 2016 talk.
        • 我确定这很复杂。我仍然觉得它可以通过增加一个间接级别来工作。