混合使用模板、std::enable_if_t和特化技术

4

编辑:根据反馈对提供的代码进行了重写以帮助说明问题。

假设我有一个方法template<T> do_foo(T),并且我仅使用std::enable_if来定义返回值。

这使我可以编写如下的签名:

// templates_top.h

template<typename T>
std::enable_if_t<std::is_fundamental<T>::value, int> do_foo(T bb)
{
    return 0;
}

我还可以在后面添加更多关于do_foo的定义——templates_top.h不需要为每个可能进入do_foo的东西都有前置声明——任何调用do_foo的地方都可以。

现在假设我有几个类,它们的do_foo实现最好不要在头文件中。

// templates_a.h
#include "./templates_top.h"
#include "./templates_b.h"

class LocalTypeA { 
  public:
    LocalTypeB* internal_;
};

template<typename T>
std::enable_if_t<std::is_same<T, LocalTypeA>::value, int> do_foo(T bb)
{
    // Some detailed business logic 
    // With recursive do_foo for member
    return do_foo<LocalTypeB>(*(bb.internal_));
}

// templates_b.h
#include "templates_top.h"

class LocalTypeB { 
    public: 
        bool the_val = false;
};

template<typename T>
std::enable_if_t<std::is_same<T, LocalTypeB>::value, int> do_foo(T bb)
{
    // Some detailed business logic here 
    // specific to LocalTypeB that ideally isn't in a header
    // Also not that do_foo is called recursively for the std::is_fundamental type
    return do_foo<bool>(bb.the_val);
}

所有这些都在头文件中运行良好。但现在 - 我们想将这个复杂的业务逻辑从标题中移出。
我们无法像 template<> do_foo(LocalTypeB) 一样完全专业化它,因为没有通用实现 template<typename T> do_foo(T)。如果我们使其通用,则 std::is_fundamental 重载变得模糊。
有没有办法让我编写此场景,以便
  • templates_top.h 保留其对 LocalTypeA/B 的无知和对 std::is_fundamental 的使用
  • 允许 LocalTypeA/B 的重载移到各自的实现文件中,并仍然解决 std::enable_if 签名函数
FWIW - 我的目标 C++ 标准是 C++11,但我猜测来自 C++14/17 的特性可能会使这成为可能,所以它们也很好。
值得说明的是,这只是我曾经遇到的一个现实问题的相当大的简化,我一直有些难以解释——我更希望能找到在更大规模上解决类型冲突核心的方法。
代码示例 在这里 - 编译为 g++ main_a.cppg++ main_b.cpp

顺便问一下,您是否想交换 TR 的顺序以启用对 T 的推导? - 463035818_is_not_a_number
@AndrewLipscomb 前往 https://godbolt.org/ 并在那里编写一个能够重现你的问题的代码,然后创建一个链接并提供在问题中。这样就可以清楚地了解问题所在以及如何解决它。 - Marek R
这里是“修正”版本 https://godbolt.org/z/4ifZB2,但我们仍然不知道为什么需要第一个 do_foo,所以这可能对您没有帮助。您必须展示模板如何用于不同类型,以便清楚为什么需要特化。 - Marek R
我知道你可以基本上添加一个结构体,在其中为我们期望使用的所有类型添加一个“true_type”,并为所有不可考虑的内容添加一个“false_type” - 但这会在头文件中添加更多代码。我正在尝试从头文件中提取代码以改善编译时间。 - Andrew Lipscomb
@MarekR 重写了问题,试图强调我在这里尝试实现的内容。如果我的意图不清楚,请再看一遍-谢谢。 - Andrew Lipscomb
显示剩余7条评论
1个回答

2

函数无法进行部分特化。

SFINAE确实可以用于丢弃重载项。

在这里,标签分派似乎更为适当:

template <typename T> struct Tag{};

template <typename T>
auto do_foo(Tag<bool>, T data)
{
    return true;
}

template <typename T>
void do_foo(Tag<void>, T data) = delete;

// ...

template <typename R, typename R>
auto do_foo(T data) -> decltype(do_foo(Tag<R>{}, data))
{
    return do_foo(Tag<R>{}, data);
}

感谢您的回答 - 我正在尝试使其工作,以便为我正在撰写的更好的代码示例进行编辑,并将在今天下班后完成问题。这里是我目前的进展 - 对于所有头文件中的内容都可以正常工作,但现在我想要完全专门化一些重载并将实现移入实现文件中。这就是我遇到问题的地方 - 在std::enable_if_t的全面专业化旁边。也许稍后再来查看更好的解释问题。 - Andrew Lipscomb
完全特化应该可以工作,但返回类型不应该是 auto。我建议只需添加(非模板)重载即可,例如: char do_foo(Tag<int>, double); - Jarod42
我更新了问题,尽力更好地解释我想要实现的内容。我尝试了基于标签的重载,但仍然找不到一种方法来从头文件中获取所有内容 - 我还是缺少了些什么。 - Andrew Lipscomb
你使用的所有类型都已知吗?如果不是,你需要进行类型擦除,以便将实现移动到cpp中。 - Jarod42

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