C++模板元函数

C++ Template Metafunction

本文关键字:函数 C++      更新时间:2023-10-16

我正在为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*)

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

请注意,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""),而不必指定要调用的函数。

正如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) 点):两个同名的函数;参数选择正确的一个。

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

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

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

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

因此,您的问题必须有一个完全不同的解决方案!

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

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

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

例如

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

将导致相同的编译器错误,因为下界A可转换为BB也不能转换为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();
};

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

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,则问题出在三元上,最后两个操作数必须可转换为相同的类型。对于一个更简单的示例,请考虑以下情况:

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

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

对于 C++11,您可以使用标记调度在编译时选择重载:

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();
}