功能模板的默认后备重载

时间:2018-11-29 11:38:52

标签: c++ templates overloading

简短的一般问题:

是否可以提供功能模板的默认“后备”重载?我在此处介绍了有关堆栈溢出的一些技术,但是其中一种要求使用可变参数函数(f(...)),这对我来说有一些严重的缺点,而另一种则需要列出所有重载的类型组合,这太冗长了,自动化程度不足以满足我的需求。这个问题被问到了,但是它已经存在了几年了,所以我想知道是否有使用最新标准的功能对此进行一些新的解决方案,甚至可能某些在C ++ 20中使用概念可以实现的解决方案。 / p>

长而详细的问题:

我正在尝试在C ++中实现诸如动态类型之类的东西。关键是,我有几种“抽象”类型,例如BoolIntegerDecimalString等,它们用一些内置类型表示,例如{{ 1}}作为Integer存储,但是除long long以外的任何整数类型都将转换为它。

现在,我开始实现运算符。我可以手动实现所有可能的重载(即boolBool + Integer等),但是我希望每个“类别”(例如Integer + Integer)只有一个实现,其中{ {1}}指抽象类型的某些层次。我具有所有必需的元功能,以区分实现的类别和层次结构。然后,我将SFINAE与这些元函数一起使用以提供定义。例如:

Decimal + any_lower_type_than<Decimal> (commutative)

这样就可以了,但是我现在需要的是一些默认的“后备”实现,当找不到匹配项时,这是万不得已的方法。因此,我需要以某种方式强制将后备实现方式设为可能的最差匹配,但同时又要针对任何情况进行匹配。

我了解到可变参数函数“ sinkhole”的匹配度比任何情况都差。因此,自然的做法是将后备提供为any_lower_type_than<Integer>,这当然是不可能的。所以我将所有运算符都更改为常规功能,例如// decimal + lower, commutative: template <typename first_t, typename second_t, std::enable_if_t<commutative_with_lower<first_t, second_t, Decimal>::value>* = nullptr> Decimal operator+ (const first_t& first, const second_t& second) { return first + second; } // string * lower than integer, commutative: template <typename first_t, typename second_t, std::enable_if_t<commutative_with_lower_than<first_t, second_t, String, Integer>::value>* = nullptr> String operator* (const first_t& first, const second_t& second) { String string_arg = is_string<first_t>::value ? first : second; auto other_arg = is_string<first_t>::value ? second : first; String result = ""; for (decltype(other_arg) i = 0; i < other_arg; ++i) result += string_arg; return result; } ,然后通过调用适当的operator+ (...)重载在更高级别上实现operator_add本身,其中默认重载被实现为可变参数operator+。但是后来我遇到了另一个问题。可变参数函数仅接受平凡类型。但是我也需要对用户定义的类型使用这种技术。

所以问题是:可变参数函数下沉孔是否有其他替代技术可以确保最差的匹配?也许甚至有一种方法可以不修改功能参数而进行修改,例如通过修改模板参数,以便我可以在具有固定功能参数签名的实际运算符上使用该技术?

3 个答案:

答案 0 :(得分:1)

向您的operator_add添加另一个参数,包括后备。

为后备的额外参数提供与其余参数(例如long)不同的类型(例如int)。

使用operator+自变量调用int,以使后备转换效果更差。

答案 1 :(得分:1)

  

所以问题是:可变参数函数沉孔是否有其他替代技术可以确保最差的匹配?

您可以使用层次结构来排序重载:

template <std::size_t N> struct overloadPriority : overloadPriority<N -1> {};
template <> struct overloadPriority<0>{};

然后

template <typename T>
std::enable_if_t<MyTrait5<T>::value> foo_impl(T&&, overloadPriority<5>) {/*..*/}

template <typename T>
std::enable_if_t<MyTrait4<T>::value> foo_impl(T&&, overloadPriority<4>) {/*..*/}

// ...

template <typename T>
void foo_impl(T&&, overloadPriority<0>) {/*..*/} // Fallback


template <typename T>
auto foo(T&& t)
{
    return foo_impl(std::forward<T>(t), overloadPriority<42>{});
    // number greater or equal to max overload_priority used by that overload.
}

答案 2 :(得分:1)

您可以简单地定义后备以包含尾随参数包:

template <typename A, typename B, typename... T>
whatever_t operator_add(A&& a, B&& b, T...)
{
    …
}

重载总是比模板更喜欢非模板,并且相对不那么专业的模板更喜欢更专业的模板。基于[temp.deduct.partial]/11,如果两个功能模板同样适合,则带有尾随参数包的功能模板会丢失。

此方法的唯一缺点是,如果您计划添加operator_add()的重载,而这些重载需要两个以上的参数,则可能会有潜在的问题。另外,如果有人不小心传递了两个以上的参数,则会调用后备选项,然后将其吞下。不幸的是,由于Jarod42的注释中提到的问题,我们不能简单地使用std::enable_if来消除重载,以防实际上碰巧有参数包的参数(就像我最初建议的那样)。您至少可以防止意外调用的一件事就是添加另一种在这种情况下将被调用并将其定义为已删除的重载:

template <typename A, typename B, typename C, typename... T>
void operator_add(A&&, B&&, C&&, T...) = delete;