【问题标题】:Using SFINAE, how to avoid 'has no member named ...'使用 SFINAE,如何避免“没有名为...的成员”
【发布时间】:2016-05-15 18:53:13
【问题描述】:

this question, which asks about SFINAE 开始,它给出了以下示例:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

但是,如果一个对象没有例如 toString() 函数,而不是这种情况返回“toString not defined”,即使我们可以检测到该函数是否存在,编译器仍然会抛出在突出显示指针 toString 调用之前,对象没有名为“toString”的成员的错误。

我希望能够对来自具有不同命名约定的 C++ 库的对象执行相同的操作,例如:

if(R_Contains_SetPosition<TemplateObject>::Value)
{
TemplateObject->SetPosition(X,Y);
}
else if(R_Contains_setPosition<TemplateObject>::Value)
{
TemplateObject->setPosition(X,Y); //TemplateObject doesn't have a setPosition defined!
}

代码已经可以执行的操作,但是编译器会在调用对象未定义的函数的任何语句上引发错误。

有没有办法让编译器接受能够调用成员函数(在这种情况下无论如何都不会运行)的代码(通过重写或修改编译器标志,最好是前者) ),即使编译器知道所说的成员函数不存在?

澄清:

这不是问如何检测函数是否存在。我已经有这个能力了。,它在问我如何编写代码来引用一个对象不一定有的函数,而编译器不会抱怨它?

系统是 C++03,因此类似 C++14 的实验性解决方案在此无效。

【问题讨论】:

  • 没有。函数中的所有代码都是实例化的,并且必须对呈现的类型有效。您必须使用 sfinae 和专业化来制作始终适用于所有给定类型的函数。 boost::hana 的代码使这更容易,看起来更像你的建议,但它是 sfinae 的语法糖。
  • 呃...这不是您链接到自己的问题的完整副本吗?在发布此问题之前,您是否阅读了那里的答案?我很想关闭它,但如果我遗漏了什么,我会给你机会回复。
  • 因为执行 ->toString,即使使用建议的函数检查器,也会引发成员函数不存在的错误。我不是在问“我如何检查一个函数是否存在”,我是在问“我如何欺骗编译器不让我知道一个函数不存在”。
  • @c1646091 那里的答案已经涵盖了这一点。 Johannes Schaub 的回答很好地涵盖了这一点。
  • Johannes Schaub 对分析的回答在很大程度上是不完整的(缺少尖括号,没有返回类型,也没有关于如何调用他的 enable_if 的示例——这似乎违反了保留关键字,因此必须重命名)。虽然它是伪代码,但我不知道如何确保它正常工作。

标签: c++ sfinae c++03 member-functions


【解决方案1】:

您的问题是,无论采用哪个分支,都会编译所有分支。所以都必须包含有效的代码。

有几种方法可以绕过它。标记调度和 SFINAE 是最常见的两种,但都不允许你“内联”。

但是,我们可以通过编写 static_if 帮助程序并使用 C++14 通用 lambda 来使用它们内联:

namespace details {
  template<class T, class F, class Else>
  decltype(auto) static_if( std::true_type, T&&t, F f, Else e ){
    return f(std::forward<T>(t));
  }
  template<class T, class F, class Else>
  decltype(auto) static_if( std::false_type, T&&t, F f, Else e ){
    return e(std::forward<T>(t));
  }
}

template<template<class...>class Test, class T, class F, class Else>
decltype(auto) static_if( T&&t, F f, Else e ){
  return details::static_if( Test<T>{}, std::forward<T>(t), std::move(f), std::move(e) );
}

这是一个 static_if 助手。

can_foo&lt;?&gt;。然后你可以这样使用它:

auto r =
  static_if<can_foo>(
    t,
    [](auto&& t){return t.foo();},
    [](auto&&){return 7;}
  );

C++14:C++11 解决方案一般不能内联完成,所以你不妨标记调度。

live example.

请注意,static_if 的返回类型会根据根据第一个参数的类型评估的模板谓词是真还是假而有所不同。

该参数被传递给真/假分支的 lambda。通常,您会重用名称(隐藏更全局的名称),因为 lambda 中的名称“神奇地”是正确的类型(基本上,只有在模板谓词在该类型上返回 true 时才会评估)。

【讨论】:

  • "std::void_t" 在我的编译器中报告为不是 std 的成员。我认为这个编译器可以使用的最新版本是 C++11。我不知道如何标记调度,也不完全知道它是如何工作的。我假设在编译器查找时它会根据返回的布尔值选择正确的分支作为函数定义查找,但我不完全理解它是如何工作的,也不知道如何转换 C++14转换为 C++11 或更早版本的兼容代码。
  • @c16 在 C++11 中你不能内联,你链接到的问题的答案是你唯一的解决方案。标签调度信息可以在无数的堆栈溢出答案中找到。我的代码只是标记调度“内联”,但它需要 C++14 来完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-09
  • 1970-01-01
  • 2016-07-23
  • 1970-01-01
  • 2015-05-23
相关资源
最近更新 更多