【问题标题】:C++ Template MetafunctionC++ 模板元函数
【发布时间】:2018-04-03 19:57:51
【问题描述】:

我正在为 Win32 的 SQL ODBC API 开发一个包装器,并且经常有几个函数,如 GetXXXTextAGetXXXTextW。我想根据用户输入类型选择适当的GetAGetW。我试过这个:

// test getterA
int _stdcall pruebaA (int, char*, const char*)
{ return 0; }
// test getterW
int _stdcall pruebaW(int, wchar_t*, const wchar_t*)
{ return 0; }
template<typename T>
struct only_char_or_wchar_t
{
    using ct = std::enable_if_t<std::is_same<T, char>::value || std::is_same<T, wchar_t>::value, T>;
};

template<typename char_type> struct char_or_wchart_api: only_char_or_wchar_t<char_type>
{
    constexpr static std::conditional_t<std::is_same<char_type, wchar_t>::value, int (_stdcall*)(int, wchar_t*, const wchar_t*) , int(_stdcall*)(int, char*, const char*)> prueba =
        std::is_same<char_type, wchar_t>::value
        ?
        ::pruebaW :
        ::pruebaA;
};

int main () {
    auto p2 = char_or_wchart_api<wchar_t>::prueba;
    p2(0, nullptr, L"");
    return 0;
}

但是,Visual Studio 2017 一直在抱怨(在“::pruebaA;”行):

Error C2446: ':': no conversion from 'int (__stdcall *)(int,char *,const char *)' to 'int (__stdcall *)(int,wchar_t *,const wchar_t *)'

即使智能感知在“调用”p2(.....)(int, wchar_t*, const wchar_t*) 时正确解析

你知道这段代码有什么问题吗?

【问题讨论】:

    标签: c++ templates winapi c++14 template-meta-programming


    【解决方案1】:

    注意pruebaApruebaW 有不同的类型:

    int _stdcall pruebaA(int, char*, const char*)
    int _stdcall pruebaW(int, wchar_t*, const wchar_t*)
    

    第一个函数与第二个函数采用不同的参数类型,因此这两种函数类型不兼容。您不能从三元组返回指向它们的指针,因为三元组中的两种类型必须兼容。


    但是,您将其复杂化了。只需要写一个重载函数:

    // Choose better names for the arguments
    int prueba(int arg1, char* arg2, const char* arg3) {
        return pruebaA(arg1, arg2, arg3);
    }
    
    int prueba(int arg1, wchar_t* arg2, const wchar_t* arg3) {
        return pruebaW(arg1, arg2, arg3);
    }
    

    这也简化了使用,因为您只需编写prueba(0, nullptr, L""),而不必指定要调用的函数。

    【讨论】:

      【解决方案2】:

      正如 Klaus(以及 Justin 和 R Sahu)所解释的,您的三元运算符收到两个不兼容的对象。

      但是,如果你使用模板特化,你就不需要only_char_or_wchar_t 并且(可能使用auto 来表示类型)都变得更简单了

      template <typename>
      struct char_or_wchart_api;
      
      template <>
      struct char_or_wchart_api<char>
       { static constexpr auto prueba = ::pruebaA; };
      
      template <>
      struct char_or_wchart_api<wchar_t>
       { static constexpr auto prueba = ::pruebaW; };
      

      但更好的解决方案(恕我直言)是贾斯汀(和克劳斯,第(2)点)提出的解决方案:两个具有相同名称的函数;选择正确的参数。

      【讨论】:

      • 这是我一直在寻找的答案,我不知道三元运算符的限制。事实上,我在我的例子中犯了一个错误,它会改变我的问题的条件,因为pruebaApruebaW都有相同的原型,所以我不能让编译器通过参数推导来选择。
      • @yo_gdg - 是的......如果pruebaA()pruebaW() 具有相同的签名......问题完全改变并且简单的重载不起作用。但是,此时,三元运算符应该可以工作......
      【解决方案3】:

      我相信你有很多错误的假设!

      1) 条件/三元运算符不能有两种不同的返回类型。因为这两个表达式必须是相同的类型,或者必须能够隐式转换为第一个!

      2) 如果你的表达式的类型在编译时是明确的,你不需要手动选择要调用的函数。这是从编译器完成的!一个简单的过载非常适合这种情况。根本不需要模板,也不需要 SFINAE 或 constexpr if。

      3) 如果你有用户输入,你不能用 constexpr/std::is_same 来决定,因为所有模板参数必须在编译时知道。您不能将运行时数据放入模板参数中!

      既然如此,你的问题肯定有一个完全不同的解决方案!

      【讨论】:

        【解决方案4】:

        你知道这段代码有什么问题吗?

        问题是由定义pruebaA时的条件表达式引起的。

        条件表达式的第二项和第三项不能完全不相关。

        例如

        struct A {};
        struct B {};
        
        bool v = true;
        (v ? A() : B());
        

        将导致相同的编译器错误,因为下界 A 可转换为 B 也不 B 可转换为 A

        在您的情况下,造成问题的两种类型是int (__stdcall *)(int,char *,const char *)int (__stdcall *)(int,wchar_t *,const wchar_t *)

        您可以使用另一个元函数来帮助您实现意图。

        template <typename T> struct func_selector;
        
        template <> struct func_selector<char>
        {
           using type = int(*)(int, char*, const char*);
           constexpr static type get() { return pruebaA; }
        };
        
        template <> struct func_selector<wchar_t>
        {
           using type = int(*)(int, wchar_t*, const wchar_t*);
           constexpr static type get() { return pruebaW; }
        };
        

        并使用

        template<typename char_type> struct char_or_wchart_api: only_char_or_wchar_t<char_type>
        {
           constexpr static auto prueba = func_selector<char_type>::get();
        };
        

        【讨论】:

          【解决方案5】:

          我不相信你需要这么荒谬的东西,并且可能只是重载一个函数来处理这个问题:

          char* GetXXXTextA(char*);
          wchar_t* GetXXXTextW(wchar_t*);
          
          // your overloads, same name
          char*    GetXXXText(char* arg) { return GetXXXTextA(arg); }
          wchar_t* GetXXXText(wchar_t* arg) { return GetXXXTextW(arg);
          

          如果您坚持使用 TMP,问题出在三元组,最后两个操作数必须可转换为相同类型。对于一个更简单的示例,请考虑this

          #include <type_traits>
          struct A { };
          struct B { };
          
          int main() {
            auto result = std::is_same<int, int>{} ? A{} : B{};
          }
          

          虽然在编译时可以清楚地看到这就像拥有A result = A{},但它是无效的,因为AB 不兼容。

          有不同的方法可以解决这个问题,对于 C++11,你可以使用 tag dispatching 在编译时选择一个重载:

          struct A { };
          struct B { };
          
          A f(std::true_type) {
            return {};
          }
          
          B f(std::false_type) {
            return {};
          }
          
          int main() {
            auto result = f(std::is_same<int, int>{});
          }
          

          对于 C++17,你可以使用if constexpr

          #include <type_traits>
          
          struct A { };
          struct B { };
          
          auto f() {
            if constexpr (std::is_same<int, int>{}) {
              return A{};
            } else {
              return B{};
            }
          }
          
          int main() {
            auto result = f();
          }
          

          【讨论】:

          • 这里的用例是什么?如果已经有不同的类型,我可以使用简单的重载,不需要标签调度。获得第三种类型的标记取决于相同的根数据类型,所以我在这里看不到好处。
          • @Klaus 我不推荐它,但 OP 正在尝试展示一些 TMP 肌肉,即使它不必要地复杂,所以我只是展示他们实际寻找的技术。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-01
          • 1970-01-01
          • 2010-12-19
          相关资源
          最近更新 更多