【问题标题】:C++ ambiguous template overloadsC++ 模棱两可的模板重载
【发布时间】:2019-02-13 18:08:17
【问题描述】:

我正在尝试使用模板特化来根据模板变量的值返回不同的类型。

我已经不再尝试在运行时分支,而不是在编译时使用typeof()、非专业模板和使用std::enable_if_t<>。我认为这可能源于对模板函数的解析方式缺乏了解。

class Test
{
public:
    template <typename T>
    T request()
    {
        T ret = getVal<T>();
        return ret;
    }

private:
    float foo = 2.f;
    int bar = 1;

    template <typename T>
    typename std::enable_if<std::is_same<T, float>::value, bool>::type
    getVal() { return foo; }

    template <typename T>
    typename std::enable_if<std::is_same<T, int>::value, bool>::type
    getVal() { return bar; }

    template<typename T>
    T getVal()
    {
        std::cout << "T is type " << typeid(T).name() << std::endl;
        throw std::bad_typeid();
    }
};

int main()
{
    Test t;
    int i;
    float f;
    i = t.template request<int>();
    f = t.template request<float>();
}

我希望这可以解决三个不同的功能,但我不确定是不是这样:

T Test::getVal()

int Test::getVal()

float Test::getVal()

任何帮助将不胜感激。

【问题讨论】:

  • 为什么不使用普通的模板特化?像下面这样的东西可能会起作用:template&lt;typename T&gt; T getVal(){ /* default impl */ } ... template&lt;&gt; int getVal&lt;int&gt;(){ return bar; } ... template&lt;&gt; float getVal&lt;float&gt;(){ return foo; }
  • 您将在这里遇到的一个问题是getVal&lt;int&gt; 将匹配getVal 的第二个和第三个声明,并且会模棱两可。你在这里的最终目标到底是什么?如果问题只是出于好奇,这是使用模板的非常不寻常的方式,更好的示例可能有助于更好地理解。
  • 我认为这个问题不能完全回答同一个问题,因为GC::Allocate&lt;A&gt; 在这种情况下成功解决,而Test::request&lt;int&gt;() 在这里没有解决。在这种情况下,将附加参数传递给函数的解决方案没有帮助,因为目标是从另一个类的初始化程序列表中的 Test 获取值。虽然这超出了示例的范围
  • 要解决模棱两可的问题,您需要将最后一个版本更改为仅适用于不是intfloat 的类型。以typename std::enable_if&lt;!std::is_same&lt;T, int&gt;::value &amp;&amp; !std::is_same&lt;T, float&gt;::value, bool&gt;::type getVal 为例。
  • “虽然这超出了示例的范围”,但并非如此。你想通过这段代码实现什么是完全相关的,只有当你告诉我们你想实现什么时,我们才能告诉你如何实现它,只是修复编译器错误永远不会导致代码干净

标签: c++ templates template-specialization


【解决方案1】:

您可以通过专门化 getVal() 轻松做到这一点,尽管您可能过于简化了问题

class Test {
public:
    template <typename T>
    T request() {
        T ret = getVal<T>();
        return ret;
    }

private:
    float foo = 2.f;
    int bar = 1;

    template<typename T>
    T getVal() {
        std::cout << "T is type " << typeid(T).name() << std::endl;
        throw std::bad_typeid();
    }
};

// add specializations
template<>
float Test::getVal<float>() { return foo; }

template <>
int Test::getVal<int>() { return bar; }

int main() {
    Test t;
    int i = t.request<int>(); // don't need template keyword here
    float f = t.request<float>();
}

如果您能够使用 c++17,则使用 if constexpr 和单个 getVal 会更简单

template<typename T>
auto getVal() {
  if constexpr (std::is_same_v<int, T>) {
    return foo;
  } else if constexpr (std::is_same_v<float, T>) {
    return bar;
  } else {
    std::cout << "T is type " << typeid(T).name() << std::endl;
    throw std::bad_typeid();
  }
}

【讨论】:

  • 注意专业化在使用前应始终可见 (ODR)。
  • 使用 C++17 解决方案最终完美运行
  • 最好将throw 替换为static_assert(always_false&lt;T&gt;, "!") 以产生编译时错误而不是运行时错误。
【解决方案2】:

您的问题是,当 SFINAEd 成功时,template&lt;typename T&gt; T getVal() 使呼叫模棱两可。

一种解决方案是用补码条件限制那个...

但是标签调度是解决问题的一种简单的替代方法:

template <typename> struct Tag{};

class Test
{
public:
    template <typename T>
    T request() const
    {
        return getVal(Tag<T>{});
    }

private:
    float foo = 2.f;
    int bar = 1;

    float getVal(Tag<float>) const { return foo; }
    int getVal(Tag<int>) const { return bar; }

    template<typename T> void getVal(Tag<T>) = delete;
};

【讨论】:

    猜你喜欢
    • 2015-01-12
    • 2019-08-04
    • 2010-12-10
    • 2015-10-12
    • 2016-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    相关资源
    最近更新 更多