【问题标题】:Base class C++14 template functions are not visible in clang on Mac os ( recursive templates)基类 C++14 模板函数在 Mac os 上的 clang 中不可见(递归模板)
【发布时间】:2018-01-26 05:03:34
【问题描述】:

此代码可以在 gcc 上正常使用,但它不适用于带有 clang 的 mac OS 该代码通过继承类型列表中的下一个类型来利用递归模板 考虑一个实体 => 头部和实体... => 尾部...

template<typename ...Entities>
    struct GenericBidderCacheLoader;

    template<typename Entity, typename ...Entities>
    struct GenericBidderCacheLoader<Entity, Entities ...> :  GenericBidderCacheLoader<Entities ...> {
        using GenericBidderCacheLoader<Entities ...>::retrieve;

        template<typename Config>
        GenericBidderCacheLoader(const Config &config): GenericBidderCacheLoader<Entities...>(config), entity(config)
        {}
        void load() noexcept(false) {
           entity.load();
           GenericBidderCacheLoader<Entities...>::load();
        }

        template<typename T, typename... Keys>
        decltype(std::declval<Entity>().retrieve(std::declval<T&>(),std::declval<Keys>()...), bool())
        retrieve(T & t, Keys&& ... keys) {
            return  entity.template retrieve(t, std::forward<Keys>(keys)...);
        }

        Entity entity;
    };

template<typename Entity>
    struct GenericBidderCacheLoader<Entity> {


        template<typename Config>
        GenericBidderCacheLoader(const Config &config): entity(config)
        {}
        void load() noexcept(false) {
            entity.load();
        }

        template<typename T, typename... Keys>
        decltype(std::declval<Entity>().retrieve(std::declval<T&>(), std::declval<Keys>()...), bool())
        retrieve(T & t, Keys&& ... keys) {
            return  entity.template retrieve(t, std::forward<Keys>(keys)...);
        }

        Entity entity;
    };

基本上 GenericBidderCacheLoader 持有一个 typeist ,并且继承模型的工作方式类似于 GenericBidderCacheLoad 派生自 GenericBidderCacheLoad 派生自 GenericBidderCacheLoad 如果您想构建具有委托功能的类型列表,这是非常常见的。

GenericBidderCacheLoader 类型的用例在这里:

using CacheLoader  =  vanilla::GenericBidderCacheLoader<RefererEntity<>, ICOCampaignEntity<>, AdDataEntity<BidderConfig>>;   
...
...
CacheLoader cacheLoader(config);    
 //Return from each lambda becomes input for next lambda in the tuple of functions
auto retrieve_referer_f = [&cacheLoader](const std::string& ref, auto&& ...) {
    Referer referer;
    if(!cacheLoader.retrieve(referer,ref)) {
        return boost::optional<uint32_t>();
    }
    return boost::optional<uint32_t>(referer.ref_id);
};

auto retrieve_ico_campaign_f = [&cacheLoader](boost::optional<uint32_t> ref_id, auto&& ...)  {
    std::vector<ICOCampaign> ico_campains;
    if (!cacheLoader.retrieve(ico_campains,*ref_id)) {
        return boost::optional<decltype(ico_campains)>();
    }
    return boost::optional<decltype(ico_campains)>(ico_campains);
};


auto retrieve_campaign_ads_f = [&cacheLoader](boost::optional<std::vector<ICOCampaign>> campaigns, auto && req, auto && imp)  {
    std::vector<Ad> retrieved_cached_ads;
    for (auto &campaign : *campaigns) {
        if (!cacheLoader.retrieve(retrieved_cached_ads, campaign.campaign_id, imp.banner.get().w, imp.banner.get().h)) {
            continue;
        }
        auto budget_bid = selector.authorize(cacheLoader.get_entity<CampaignCache<BidderConfig>>(), campaign.campaign_id);
        std::transform(std::begin(retrieved_cached_ads),
                       std::end(retrieved_cached_ads),
                       std::begin(retrieved_cached_ads), [budget_bid](Ad & ad){
                    ad.auth_bid_micros = std::min(budget_bid, ad.max_bid_micros);
                    return ad;
                });
    }
    if ( retrieved_cached_ads.empty() ) {
        return boost::optional<decltype(retrieved_cached_ads)>();
    }
    return boost::optional<decltype(retrieved_cached_ads)>(retrieved_cached_ads);
};

错误信息:

error: no matching member function for call to
      'retrieve'
        if (!cacheLoader.retrieve(ico_campains,*ref_id)) {
             ~~~~~~~~~~~~^~~~~~~~
generic_bidder_cache_loader.hpp:42:9: note: candidate template ignored: substitution failure [with
      T = std::__1::vector<ICOCampaign, std::__1::allocator<ICOCampaign> >, Keys = <unsigned int &>]: non-const lvalue reference to type 'Referer'
      cannot bind to a value of unrelated type 'std::__1::vector<ICOCampaign, std::__1::allocator<ICOCampaign> >'
        retrieve(T & t, Keys&& ... keys) {

如果是 GCC

using GenericBidderCacheLoader<Entities...>::retrieve 

帮助GCC查看所有基类中定义的那些函数,但是clang放弃了看不到? 理想情况下,它应该如下工作,当替换失败 SFINAE 时,它应该能够在任何派生、base-I、base-II、base-III 类中看到函数。

完整的实现可以在 github 上的临时分支中找到 https://github.com/venediktov/vanilla-rtb/tree/ico_bidder20171217

https://github.com/venediktov/vanilla-rtb/blob/ico_bidder20171217/examples/ico_campaign_bidder/ico_campaign_bidder_test.cpp

https://github.com/venediktov/vanilla-rtb/blob/ico_bidder20171217/rtb/datacache/generic_bidder_cache_loader.hpp

我也尝试做递归来帮助Clang看到那些递归实例化的类函数

template<typename T, typename... Keys>
using retrieve_type = decltype(std::declval<Entity>().retrieve(std::declval<T&>(), std::declval<Keys>()...)) ;

      template<typename T, typename... Keys>
      typename std::enable_if<std::is_same<retrieve_type<T,Keys...>,bool>::value,bool>::type
        retrieve(T & t, Keys&& ... keys) {
            return  entity.template retrieve(t, std::forward<Keys>(keys)...);
        }

        template<typename T, typename... Keys>
        typename std::enable_if<std::is_same<typename GenericBidderCacheLoader<Entities ...>::template retrieve_type<T,Keys...>,bool>::value,bool>::type
        retrieve(T & t, Keys&& ... keys) {
            return  GenericBidderCacheLoader<Entities ...>::retrieve(t, std::forward<Keys>(keys)...);
        }

但它只有助于在类型列表中看到第二个而不是第三个,这几乎是我不得不从顶层递归到最终类型的情况。

【问题讨论】:

  • 你能提供 clang 错误吗?我尝试simplify the problem,但当仅限于模板成员函数时,clang 没有问题(可能是一些事情搞砸了)
  • 无法复制,请发minimal reproducible example
  • @vu1p3n0x 您的简化问题与 OP 的代码不同。事实上,clang++ 和 g++ 在这个问题上的表现确实不同。
  • @liliscent,感谢您创建最小、完整和可验证的示例!

标签: c++ templates c++14 sfinae llvm-clang


【解决方案1】:

此问题缺少最小、完整且可验证的示例。所以我还没有测试过OP的代码。但是,通过阅读引用的代码片段和错误消息,我构建了一个最小示例,可以使 g++ 和 clang++ 之间的不同行为更加清晰:

#include <utility>
#include <type_traits>

template<class U>
struct Foo
{
    template<class V>
    decltype(std::declval<U>().echo_dumb(std::declval<V>()))
    dumb(V const &v) {
        u.echo_dumb(v);
    }
    U u;
};

template<class T, class U>
struct Bar
    : public Foo<U>
{
    using Foo<U>::dumb;

    template<class V>
    decltype(std::declval<T>().echo_dumb(std::declval<V>()))
    dumb(V const &v) {
        t.echo_dumb(v);
    }
    T t;
};

struct A { void echo_dumb(A const &) {} };
struct B { void echo_dumb(B const &) {} };

int main() {
    Bar<A, B> bar;
    bar.dumb(A{}); // ok for everyone
    bar.dumb(B{}); // error for clang++, ok for g++
}

因此问题变成了“using 声明的语义是什么?”。

在阅读了一些C++标准页面后,我认为clang++正确拒绝了这段代码,因为:

[namespace.udecl]

通过对 using-declarator 中的名称执行限定名称查找 ([basic.lookup.qual], [class.member.lookup]) 来找到 using-declarator 引入的声明集,不包括以下函数如下所述隐藏。

...

当 using-declarator 将基类中的声明带入派生类时,派生类中的成员函数和成员函数模板会覆盖和/或隐藏同名的成员函数和成员函数模板,parameter-type-list , cv-qualification 和 ref-qualifier(如果有的话)在基类中(而不是冲突)。此类隐藏或覆盖的声明被排除在 using-declarator 引入的声明集中。

由于2个dumb函数具有完全相同的参数类型列表,using声明引入的那个是隐藏的,不会被包含。

如果Foo 中的dumb 具有签名dumb(V const &amp;v, int = 0),则g++ 和clang++ 都接受此代码。

有趣的是,如果我们将 2 decltype(std::declval&lt;U&gt;().echo_dumb(std::declval&lt;V&gt;())) 简单地替换为 void,那么 g++ 和 clang++ 都会正确拒绝它,并显示消息没有匹配函数。因此,返回部分中的 decltype 部分似乎使 g++ 编译器感到困惑。

【讨论】:

【解决方案2】:

这不是答案,我只是想展示我如何根据@liliscent 的答案修复我的代码

        template<typename T, typename... Keys>
        bool retrieve(T & t, Keys&& ... keys) {
            auto tuple = std::make_tuple(std::forward<Keys>(keys)...);
            return  this->retrieve(t, tuple, std::make_index_sequence<std::tuple_size<decltype(tuple)>::value>());
        }

        template<typename T, typename Tuple, std::size_t... Idx>
        decltype(std::declval<Entity>().retrieve(std::declval<T&>(),std::get<Idx>(std::declval<Tuple>())...), bool())
        retrieve(T & t, Tuple&& tuple, std::index_sequence<Idx...>, Entity* = 0) {
            return  entity.template retrieve(t, std::get<Idx>(std::forward<Tuple>(tuple))...);
        }

新代码现在可以同时使用 gcc 和 clang 进行编译。如果有人查看我的更改,我会很高兴。此代码是 GitHub 上的一个开源项目。如果有人想查看原始问题,我的原始问题中的文件链接已过时,请点击修订历史记录。

而使用 sizeof... 运算符,它甚至更短

    template<typename T, typename... Keys>
    bool retrieve(T & t, Keys&& ... keys) {
        return  this->retrieve(t, std::make_tuple(std::forward<Keys>(keys)...),
                                  std::make_index_sequence<sizeof...(keys)>()
                               );
    }

    template<typename T, typename Tuple, std::size_t... Idx>
    decltype(std::declval<Entity>().retrieve(std::declval<T&>(),std::get<Idx>(std::declval<Tuple>())...), bool())
    retrieve(T & t, Tuple&& tuple, std::index_sequence<Idx...>, Entity* = 0) {
        return  entity.template retrieve(t, std::get<Idx>(std::forward<Tuple>(tuple))...);
    }

【讨论】:

    猜你喜欢
    • 2017-06-28
    • 2011-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    相关资源
    最近更新 更多