【问题标题】:How to construct a type trait that can tell if one type's private methods can be called in another type's constructor?如何构造一个类型特征来判断一种类型的私有方法是否可以在另一种类型的构造函数中调用?
【发布时间】:2019-12-05 22:12:11
【问题描述】:

我正在使用 C++17。我有如下代码:

#include <type_traits>

template <typename T>
struct Fooer
{
    Fooer (T & fooable)
    {
        fooable . foo ();
    }
};

template <typename T>
Fooer (T & fooable) -> Fooer <T>;

struct Fooable
{
private:

    void
    foo ();

    friend struct Fooer <Fooable>;
};

struct NotFooable
{
};

我想实现一个类型特征,它可以判断一个类型是否是“Fooable”。

我无法检查类型上是否有方法foo (),因为它是私有方法。这也没有告诉我Fooer的构造函数是否可以调用该方法。

// Checking for the foo method doesn't work.

template <typename T, typename = void>
struct HasFoo;

template <typename T, typename>
struct HasFoo : std::false_type
{
};

template <typename T>
struct HasFoo
<
    T,
    std::enable_if_t
    <
        std::is_convertible_v <decltype (std::declval <T> () . foo ()), void>
    >
>
:   std::true_type
{
};

// Both of these assertions fail.
static_assert (HasFoo <Fooable>::value);
static_assert (HasFoo <NotFooable>::value);

我也无法检查Fooer &lt;T&gt; 是否可以通过std::is_constructible 构造,因为std::is_constructible 不会检查构造函数定义 是否格式正确,只有表达式Fooer &lt;T&gt; fooer (std::declval &lt;T&gt; ())

// Checking constructibility doesn't work either.

template <typename T, typename = void>
struct CanMakeFooer;

template <typename T, typename>
struct CanMakeFooer : std::false_type
{
};

template <typename T>
struct CanMakeFooer
<
    T,
    std::enable_if_t <std::is_constructible_v <Fooer <T>, T &>>
>
:   std::true_type
{
};

// Neither of these assertions fail.
static_assert (CanMakeFooer <Fooable>::value);
static_assert (CanMakeFooer <NotFooable>::value);

如果我真的尝试调用构造函数,我会得到预期的错误,尽管它并没有让我更接近实现类型特征。

void
createFooer ()
{
    Fooable fooable;
    NotFooable not_fooable;

    // This works fine.
    { Fooer fooer (fooable); }

    // This correctly generates the compiler error: no member named 'foo' in
    // 'NotFooable'
    { Fooer fooer (not_fooable); } 
}

我想避免将类型特征声明为 Fooable 类型的朋友,并且我想避免公开 'foo'。

如果我能以某种方式使类型特征检查函数或构造函数的 定义 是否格式正确,我可以很容易地实现这种类型特征,但我不知道该怎么做,而且我在互联网上找不到任何这样的例子。

有可能做我想做的事吗?我该怎么做?

【问题讨论】:

  • 你能改一下Fooer吗?
  • @aschepler:怎么改?

标签: c++ templates c++17 typetraits


【解决方案1】:

您需要在Fooer 构造函数的声明中调用foo(),并使构造函数对SFINAE 友好。您可以使用构造函数模板和要求的默认模板参数来执行此操作。这意味着HasFoo 只需要检查Fooer 是否可以用T 构造,而不必担心foo() 函数。

template <typename T>
struct Fooer {
  template <typename U, typename = std::void_t<
    decltype(std::declval<U &>().foo()),
    std::enable_if_t<std::is_same_v<T, U>>
  >>
  explicit Fooer(U &fooable) {
    fooable.foo();
  }
};

template <typename U>
Fooer(U &) -> Fooer<U>;

template <typename T>
struct HasFoo : std::bool_constant<
  std::is_constructible_v<Fooer<T>, T &>
> {};

struct Fooable {
private:
  void foo() {}

  friend struct Fooer<Fooable>;
};

struct NotFooable {};

static_assert(HasFoo<Fooable>::value);
static_assert(!HasFoo<NotFooable>::value);

【讨论】:

    【解决方案2】:

    这里的问题是Fooer 的构造函数不是“SFINAE-friendly”的。它要求Fooer 可以调用fooable.foo(),但就C++ 而言,声明Fooer(T &amp;); 没有任何此类约束。

    我们可以将构造函数声明改为构造函数模板,这样当类模板的模板参数不是“fooable”时,模板参数推导失败:

    #include <utility>
    
    template <typename T>
    struct Fooer
    {
        template <typename U = T, typename Enable =
                    std::void_t<decltype(std::declval<U&>().foo())>>
        Fooer (T & fooable)
        {
            fooable . foo ();
        }
    };
    

    [使用 C++20 约束,这将变得更容易、更清晰:

    // C++20 code
    template <typename T>
    struct Fooer
    {
         Fooer (T & fooable) requires requires { fooable.foo(); }
         {
             fooable . foo ();
         }
    };
    

    ]

    有了这个改变,你的CanMakeFooer 应该可以工作了。虽然它可以更简单地定义为只使用主模板而没有专门化:

    template <typename T>
    struct CanMakeFooer :
        public std::bool_constant<std::is_constructible_v<Fooer<T>, T&>>
    {};
    

    Demo on coliru.

    【讨论】:

    • Bruh_______________
    • @Kerndog73 是的,在我完成并发布我的之前没有看到你的答案出现。至少非常同意!投赞成票。
    • 马上回到你身边!我觉得有趣的是,我们都独立编写了几乎完全相同的问题解决方案
    • 我接受了这个版本,因为有额外的 C++20 答案和更简洁的代码。这正是我正在寻找的解决方案。感谢您的帮助!
    【解决方案3】:

    我对您的 HasFoo 使用了类似的方法,但使用函数而不是结构,它按预期工作。

    Fooer (T& fooable)
    {
        if constexpr (has_foo<T>(0)) {
            cout << "Yes foo" << endl;
            fooable.foo();
        }
        else {
            cout << "No foo" << endl;
        }
    }
    
    template<class Y>
    static constexpr auto has_foo(Y*) -> decltype(std::declval<Y>().foo(), bool()) {
        return true;
    }
    
    template<class Y>
    static constexpr bool has_foo(...) {
        return false;
    }
    

    https://wandbox.org/permlink/5GGY89YVLsfqeZ2A

    【讨论】:

    • 这很好。然而,它并没有给出问题的解决方案,因为 OP 想要避免让 type-trait 成为 Fooable 的朋友。您通过将类型特征放入 Fooer 中间接地做到了这一点。
    • 感谢您的反馈。从 Fooer 开始,我对这些函数如何成为 Fooable/NotFooable 的间接朋友有点困惑。
    • @super:虽然我确实更喜欢不会在Fooer 中添加额外代码的解决方案,但我的意思是我不想有两个单独的朋友声明在任何 Fooable 类中(一个用于 Fooer,一个用于 IsFooable)。这个答案避免了这种情况。如果没有更好的答案,这可能会成为公认的答案。
    猜你喜欢
    • 2018-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多