C++模板元函数

3

我正在为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;”):

错误 C2446:':':从'int (__stdcall *)(int,char*,const char*)'到'int (__stdcall *)(int,wchar_t*,const wchar_t*)'没有转换

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

您有什么想法,这段代码可能出了什么问题?

5个回答

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

3

正如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; };

但我认为更好的解决方案是Justin(和Klaus,点(2))提出的一种:两个同名函数;参数选择正确的函数。


这就是我一直在寻找的答案,我不知道三元运算符的限制。事实上,我在我的示例中犯了一个错误,它会改变我的问题条件,因为pruebaApruebaW都具有相同的原型,这就是为什么我不能让编译器通过参数推导来选择。 - yo_gdg
@yo_gdg - 是的...如果pruebaA()pruebaW()具有相同的签名...问题就完全改变了,简单的重载无法解决。但是,在这一点上,三元运算符应该可以解决问题... - max66

3
我认为您有很多错误的假设!
1)条件/三元运算符不能有两种不同的返回类型。因为这两个表达式必须是相同的类型或能够隐式转换为第一个表达式!
2)如果您的表达式类型在编译时已经明确,您不需要手动选择要调用的函数。这由编译器完成!一个简单的重载对于这种情况非常完美,根本不需要模板,也没有SFINAE或constexpr if。
3)如果您有用户输入,则无法使用constexpr/std::is_same进行决策,因为所有模板参数必须在编译时已知。您无法将运行时数据放入模板参数中!
因此,您的问题必须有一个完全不同的解决方案!

1

你有没有想法,这段代码可能出了什么问题?

问题是在定义 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();
};

0

我不认为你需要这么荒谬的东西,可能只需重载一个函数来处理:

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中,您可以使用标签分派来在编译时选择重载:

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
1
@Klaus,我并不推荐这样做,但是OP试图展示一些TMP技巧,即使它是不必要的复杂,所以我只是展示他们实际正在寻找的技术。 - Ryan Haining

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接