jjzjj

c++ - 通用 lambda、重载、std::is_invocable 和 SFINAE - GCC 和 Clang 之间的不同行为

coder 2024-02-23 原文

问题

我写了一段可以编译的复杂模板代码with GCC 8.2.1 , 但不是 with Clang 7.0 (代码和错误链接)。

我认为这可能是 this Q&A 的暗示, 但我看不到它。

动机

我正在编写一个类,我希望它可以用两个不同类型的可调用对象构造,但也可以省略其中一个,即:

my_class(callable_1);
my_class(callable_2);
my_class(callable_1, callable_2);

那应该没有问题。但是,为什么不允许 callable_1callable_2 成为函数模板(或带有 operator() 模板的仿函数)。也就是说,我想要这个(或者至少最初想要):

my_class([](auto arg) {});
my_class([](auto arg) {});
my_class([](auto arg) {}, [](auto arg) {});

如您所见,不幸的是,这两个可调用对象具有相同的签名,因此我们需要以某种方式消除它们之间的歧义。我能想到的第一种方法(也是这个问题所涉及的方法)是向其中一个一元重载添加一个“标记”参数:

my_class([](auto arg) {});
my_class([](auto arg) {}, callable_2_tag());
my_class([](auto arg) {}, [](auto arg) {});

对我来说,这看起来可以接受,但我想出了更好的解决方案:

  • 在第二个可调用对象的签名(最后一个参数或返回类型)中使用标签(如果不明确则可选)
  • 使第二个构造函数重载为不同名称的非成员函数或static 成员函数

不过,我还是想知道为什么使用我的初始方法的两个编译器在行为上存在差异,以及哪个编译器是正确的(或者两者是否都是正确的)。


代码:

为简单起见,我已将构造函数重载转换为常规 my_class 函数重载。

#include <iostream>
#include <type_traits>

// parameter types for callbacks and the tag class
struct foo { void func1() {} };
struct bar { void func2() {} };
struct bar_tag {};

// callable checks
template <typename Func>
static constexpr bool is_valid_func_1_v = std::is_invocable_r_v<void, Func, foo>;

template <typename Func>
static constexpr bool is_valid_func_2_v = std::is_invocable_r_v<void, Func, bar>;

// default values
static constexpr auto default_func_1 = [](foo) {};
static constexpr auto default_func_2 = [](bar) {};

// accepting callable 1
template <typename Func1, std::enable_if_t<is_valid_func_1_v<Func1>>* = nullptr>
void my_class(Func1&& func_1)
{
    my_class(std::forward<Func1>(func_1), default_func_2);
}

// accepting callable 1
template <typename Func2, std::enable_if_t<is_valid_func_2_v<Func2>>* = nullptr>
void my_class(Func2&& func_2, bar_tag)
{
    my_class(default_func_1, std::forward<Func2>(func_2));
}

// accepting both
template <
    typename Func1, typename Func2,
    // disallow Func2 to be deduced as bar_tag
    // (not even sure why this check did not work in conjunction with others,
    // even with GCC)
    std::enable_if_t<!std::is_same_v<Func2, bar_tag>>* = nullptr,
    std::enable_if_t<is_valid_func_1_v<Func1> &&
                     is_valid_func_2_v<Func2>>* = nullptr>
void my_class(Func1&& func_1, Func2&& func_2)
{
    std::forward<Func1>(func_1)(foo());
    std::forward<Func2>(func_2)(bar());
}

int main()
{
    my_class([](auto foo) { foo.func1(); });
    my_class([](auto bar) { bar.func2(); }, bar_tag());
}

对于 Clang,这将导致:

error: no member named 'func1' in 'bar'
my_class([](auto foo) { foo.func1(); });
                        ~~~ ^
...
note: in instantiation of variable template specialization
'is_valid_func_2_v<(lambda at prog.cc:41:14)>' requested here
template <typename Func2, std::enable_if_t<is_valid_func_2_v<Func2>>* = nullptr>
                                           ^

这里发生了什么? 替换失败是错误吗?

编辑: 我完全不知道 std::enable_if 的谓词中的错误也会被沉默......那是 不是替换失败。

修复:

如果我将 SFINAE 作为函数参数,Clang 可以很好地处理它。 我不知道为什么将检查从模板参数推导阶段推迟到重载解析阶段会有所不同。

template <typename Func2>
void my_class(Func2&& func_2, bar_tag,
              std::enable_if_t<is_valid_func_2_v<Func2>>* = nullptr)
{
    my_class(default_func_1, std::forward<Func2>(func_2));
}

总而言之,我对通用性的投入可能超出了我的知识范围,现在我正在为此付出代价。那我错过了什么?细心的读者可能会注意到突然出现的一些附带问题,但我不想回答所有这些问题。最后,如果可以制作一个更简单的 MCVE,我很抱歉。

最佳答案

据我了解,您没有正确使用 SFINAE - 如果您尝试调用 std::is_invocable_r_v<void, Func, bar>;Func == decltype([](auto foo) { foo.func1(); }你会得到一个编译器错误,因为 lambda 中的 auto 被推断为 bar然后尝试调用 func1()在上面。如果您的 lambda 没有使用 auto 而是将实际类型作为参数(即 foo ,因此您不能使用 bar 调用它),is_invocable_r_v会返回 false 并且 SFINAE 会工作。

关于c++ - 通用 lambda、重载、std::is_invocable 和 SFINAE - GCC 和 Clang 之间的不同行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53021106/

有关c++ - 通用 lambda、重载、std::is_invocable 和 SFINAE - GCC 和 Clang 之间的不同行为的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby-on-rails - 无法在centos上安装therubyracer(V8和GCC出错) - 2

    我正在尝试在我的centos服务器上安装therubyracer,但遇到了麻烦。$geminstalltherubyracerBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtherubyracer:ERROR:Failedtobuildgemnativeextension./usr/local/rvm/rubies/ruby-1.9.3-p125/bin/rubyextconf.rbcheckingformain()in-lpthread...yescheckingforv8.h...no***e

  3. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  4. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  5. ruby - 导轨 4 : column reference "updated_at" is ambiguous with Postgres - 2

    我正在尝试使用“updated_at”字段的日期时间范围查询数据库。前端在JSON数组中发送查询:["2015-09-0100:00:00","2015-10-0223:00:00"]在RailsController中,我使用以下方法将两个字符串解析为DateTime:start_date=DateTime.parse(params[:date_range_arr][0])end_date=DateTime.parse(params[:date_range_arr][1])#...@events=@events.where('updated_atBETWEEN?AND?,start_d

  6. ruby - what is - gets is a directory - 错误信息 - 2

    我遇到了这个奇怪的错误.../Users/gideon/Documents/ca_ruby/rubytactoe/lib/player.rb:13:in`gets':Isadirectory-spec(Errno::EISDIR)player_spec.rb:require_relative'../spec_helper'#theuniverseisvastandinfinite...itcontainsagame....butnoplayersdescribe"tictactoegame"docontext"theplayerclass"doit"musthaveahumanplay

  7. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  8. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

    我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

  9. ruby - 当你有一个没有参数的 case 语句并且 when 子句是 lambda 时会发生什么? - 2

    这段代码没有像我预期的那样执行:casewhen->{false}then"why?"else"ThisiswhatIexpect"end#=>"why?"这也不是casewhen->(x){false}then"why?"else"ThisiswhatIexpect"end#=>"why?"第一个then子句在两种情况下都被执行,这意味着我提供给when子句的lambda没有被调用。我知道无论when子句的主题是什么,都应该调用大小写相等运算符===。我想知道当没有为case提供参数时,===的另一边会发生什么。我在想它可能是nil,但它不可能是:->{false}===nil#=>

  10. ruby - 如何将 lambda 传递给 Hash.each? - 2

    如何将lambda传递给hash.each,以便我可以重复使用一些代码?>h={a:'b'}>h.eachdo|key,value|end=>{:a=>"b"}>test=lambdado|key,value|puts"#{key}=#{value}"end>test.call('a','b')a=b>h.each&testArgumentError:wrongnumberofarguments(1for2)from(irb):1:in`blockinirb_binding'from(irb):5:in`each'from(irb):5from/Users/jstillwell/.rv

随机推荐