c++ - 模板默认参数 SFINAE 与 clang 不明确,适用于 g++

标签 c++ templates c++11 clang sfinae

我正在从事一个项目,该项目涉及为用户提供一个界面,以找到任意数量参数的函数的最优值。在内部,所有机制都是围绕参数类型的 std::tuple 构建的。不过,我想为用户提供调用我的优化例程的能力,以“通常”风格编写的函数(例如示例中的 f1),而不是必须编写要优化的函数作为 std::tuple 实例化的函数(例如示例中的 f2)。

作为该机制的一部分,我编写了一个 apply 函数,它将一个元组解包到给定函数的参数中并调用它。

我还创建了一对函数模板,一个使用 lambda 包装器转发到另一个,提供优化例程的接口(interface)。下面显示了一个简化版本,如 tuple_array_map。目的是提供 SFINAE 以在两者之间进行选择,具体取决于函数类型是可使用元组参数调用,还是可使用未打包的元组成员作为参数调用。为此,我使用带有 SFINAE 触发默认参数的虚拟模板参数。

此方案在 g++ 4.7 及更高版本下完美运行,使用 -std=c++11 -pedantic -Wall -Wextra -Werror 编译不会产生任何警告或错误。

但是,当尝试使用 -std=c++11 在 clang 5.1 下编译时(抱歉,我不是 clang 的大用户,我不知道是否有更合适的设置选项),我的示例代码得到以下输出:

clang_fail.cpp:91:5: error: call to 'tuple_array_map' is ambiguous
    tuple_array_map(f2, tuples);
    ^~~~~~~~~~~~~~~
clang_fail.cpp:59:6: note: candidate function [with Fn = double (*)(const
      std::__1::tuple<double> &), TupleArr =
      std::__1::array<std::__1::tuple<double>, 5>, $2 = double]
void tuple_array_map(Fn f, const TupleArr& arr)
     ^
clang_fail.cpp:69:6: note: candidate function [with Fn = double (*)(const
      std::__1::tuple<double> &), TupleArr =
      std::__1::array<std::__1::tuple<double>, 5>, $2 = double, $3 = void]
void tuple_array_map(Fn f, const TupleArr& arr)
     ^
clang_fail.cpp:71:5: error: call to 'tuple_array_map' is ambiguous
    tuple_array_map([&](const typename TupleArr::value_type& t) {
    ^~~~~~~~~~~~~~~
clang_fail.cpp:90:5: note: in instantiation of function template specialization
      'tuple_array_map<double (*)(double),
      std::__1::array<std::__1::tuple<double>, 5>, double, void>' requested here
    tuple_array_map(f1, tuples);
    ^
clang_fail.cpp:59:6: note: candidate function [with Fn = <lambda at
      clang_fail.cpp:71:21>, TupleArr = std::__1::array<std::__1::tuple<double>,
      5>, $2 = double]
void tuple_array_map(Fn f, const TupleArr& arr)
     ^
clang_fail.cpp:69:6: note: candidate function [with Fn = <lambda at
      clang_fail.cpp:71:21>, TupleArr = std::__1::array<std::__1::tuple<double>,
      5>, $2 = double, $3 = void]
void tuple_array_map(Fn f, const TupleArr& arr)
     ^

真正令人费解的部分是,它似乎从应该 SFINAE 的调用表达式中推断出 double 返回,除非我错过了标准中有关模板默认参数或 SFINAE 本身的内容。

示例如下---它是我能得到的最小但仍然触发相同行为的示例:

#include <tuple>
#include <array>
#include <utility>
#include <type_traits>

double f1(double x)
{
    return x * 2;
}

double f2(const std::tuple<double>& x)
{
    return std::get<0>(x) * 2;
}

template<std::size_t N>
struct apply_impl {
    template<class F, class Tuple, class... TParams>
    static auto apply(F&& fn, Tuple&& t, TParams&&... args)
      ->  decltype(
              apply_impl<N - 1>::apply(
                  std::forward<F>(fn), std::forward<Tuple>(t),
                  std::get<N - 1>(std::forward<Tuple>(t)),
                  std::forward<TParams>(args)...
          ))
    {
        return apply_impl<N - 1>::apply(
                std::forward<F>(fn), std::forward<Tuple>(t),
                std::get<N - 1>(std::forward<Tuple>(t)),
                std::forward<TParams>(args)...
                );
    }
};

template<>
struct apply_impl<0> {
    template<class F, class Tuple, class... TParams>
    static auto apply(F&& fn, Tuple&&, TParams&&... args)
      -> decltype(std::forward<F>(fn)(std::forward<TParams>(args)...))
    {
        return std::forward<F>(fn)(std::forward<TParams>(args)...);
    }
};

template<class F, class Tuple>
auto apply(F&& fn, Tuple&& t)
  -> decltype(apply_impl<
          std::tuple_size<typename std::decay<Tuple>::type>::value
        >::apply(std::forward<F>(fn), std::forward<Tuple>(t)))
{
    return apply_impl<
        std::tuple_size<typename std::decay<Tuple>::type>::value
      >::apply(std::forward<F>(fn), std::forward<Tuple>(t));
}

template<class Fn, class TupleArr,
    class = decltype(std::declval<Fn>()(
                std::declval<typename TupleArr::value_type>()))>
void tuple_array_map(Fn f, const TupleArr& arr)
{
    for (auto i = 0; i < arr.size(); ++i)
        static_cast<void>(f(arr[i]));
}

template<class Fn, class TupleArr,
    class = decltype(apply(std::declval<Fn>(),
                std::declval<typename TupleArr::value_type>())),
    class = void>
void tuple_array_map(Fn f, const TupleArr& arr)
{
    tuple_array_map([&](const typename TupleArr::value_type& t) {
                return apply(f, t);
            }, arr);
}

int main()
{
    std::array<std::tuple<double>, 5> tuples = {
        std::make_tuple(1),
        std::make_tuple(2),
        std::make_tuple(3),
        std::make_tuple(4),
        std::make_tuple(5)
    };

    // "apply" unpacks a tuple into arguments to a function
    apply(f1, tuples[0]);

    // this call produces an ambiguity one level down under clang
    tuple_array_map(f1, tuples);
    // this call directly produces an ambiguity under clang
    tuple_array_map(f2, tuples);
}

最佳答案

使用 libc++ 编译时的歧义是由于缺少标准规定的 explicit std::tuple 上的说明符的转换构造函数(Constructor #2 at cppreference)。因此,double可隐式转换为 std::tuple<double> ( See this example program ) 所以你的 tuple_apply_mapboth功能是可行的。

作为解决方法,我建议创建一个 needs_apply特征并用它来约束你的 tuple_apply_map模板(我将使用标签调度):

template<class Fn, class TupleArr>
struct needs_apply {
    template <class F=Fn>
    static auto test(int) ->
      decltype(std::declval<F>()(*std::declval<TupleArr>().begin()), std::false_type{});
    static auto test(...) -> std::true_type;
    using type = decltype(test(0));
};

template<class Fn, class TupleArr>
void tuple_array_map(Fn f, const TupleArr& arr, std::false_type)
{
    for (auto&& i : arr)
        static_cast<void>(f(i));
}

template<class Fn, class TupleArr>
void tuple_array_map(Fn f, const TupleArr& arr, std::true_type)
{
    tuple_array_map([&](const typename TupleArr::value_type& t) {
                return apply(f, t);
            }, arr, std::false_type{});
}

template<class Fn, class TupleArr>
void tuple_array_map(Fn&& f, TupleArr&& arr) {
    tuple_array_map(std::forward<Fn>(f), std::forward<TupleArr>(arr),
                    typename needs_apply<Fn,TupleArr>::type{});
}

这可以正常工作 with libc++with libstdc++甚至 compiling with g++ .

According to this answer by Howard Hinnant, this non-conformance of the std::tuple constructor is an extension implemented in libc++ as an experiment. 另见 Library Working Group active issue 2051the paper N3680 written by Daniel Krügler to address the issue .

关于c++ - 模板默认参数 SFINAE 与 clang 不明确,适用于 g++,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24253782/

相关文章:

c++ - 使用另一个类的模板参数实例化一个模板类

c++ - 多个 emplace_back 额外调用复制构造函数

比较运算符重载与转换运算符的 C++ 优先级

c++ - clang 使用了错误的系统包含目录

c++ - 可变参数模板方法来创建对象

c++ - 跨编译器的浮点文字到 IEEE-754 二进制模式的一致性

java - 如何在 JMustache 中处理子模板中的 map 列表?

c++ - 局部变量的引用崩溃

windows - 终端中的 Ctrl+Z 行为

c++ - 编译器看不到模板特化?