【问题标题】:Possible to access private types in base classes via template indirection可以通过模板间接访问基类中的私有类型
【发布时间】:2013-07-22 16:28:54
【问题描述】:

我试图在编译时根据一个类型是否在给定范围内公开可用来选择要使用的类型。最好直接上代码:

#include <iostream>
#include <type_traits>

class Logger
{
  std::string _p;
public:
  Logger(std::string p): _p(p)
  { }

  void say(std::string message)
  { std::cout << _p << ' ' << message << std::endl; }
};

struct Log
{
  static Logger& log()
  {
    static Logger _def("Default: ");
    return _def;
  }
};

// 1.
template <typename P>
struct use_logger
{
  static std::size_t test(P*);
  static char test(...);
  static const bool value = sizeof(test(reinterpret_cast<P*>(0))) == sizeof(std::size_t);
};

class A
{
  struct Log
  {
    static Logger& log()
    {
      static Logger _def("A: ");
      return _def;
    }
  };
public:

  void say()
  {
    std::cout << "A: " << use_logger<Log>::value << std::endl;
    std::conditional<use_logger<Log>::value, Log, ::Log>::type::log().say("From A");
  }
};

class B
{
public:

  void say()
  {
    std::cout << "B: " << use_logger<Log>::value << std::endl;
    std::conditional<use_logger<Log>::value, Log, ::Log>::type::log().say("From B");
  }
};

class C : A
{
public:

  void say()
  {
    std::cout << "C: " << use_logger<Log>::value << std::endl;
    //2.
    std::conditional<use_logger<Log>::value, Log, ::Log>::type::log().say("From C");
    // Log::log().say("From C");
  }
};

class D : public A
{
public:

  void say()
  {
    // 2.
    std::cout << "D: " << use_logger<Log>::value << std::endl;
    std::conditional<use_logger<Log>::value, Log, ::Log>::type::log().say("From D");
    // Log::log().say("From C");
  }
};

int main(void)
{
  {
    A i;
    i.say();
  }
  {
    B i;
    i.say();
  }
  {
    C i;
    i.say();
  }
  {
    D i;
    i.say();
  }
}

我的意图是在A 中有一个类型Log,所以应该使用它而不是全局::Log,并且在B 中没有类型,它应该使用全局@987654327 @。现在这两个工作都不管1. (我不正确的测试,看看类型是否在这个范围内是 private..)

问题出在CD,通常情况下 - 如果没有测试,Log::log() 会失败,因为它在 A 中是私有的。但是,如果使用std::conditional&lt;&gt;,则不会出现编译错误,并且输出不正确,因为它以A: 为前缀。那么,我错过了什么(除了不正确的测试 - 我需要以某种方式修复......)?如果没有,那么这种使用std::conditional 公开A 中的私有类型的方法是否合法?

编辑:为了理智,我测试了以下内容:

std::conditional<false, Log, ::Log>::type::log("From C");
std::conditional<false, Log, ::Log>::type::log("From D");

它确实使用了全局::Log,如果是真的,它在某种程度上使用了私有A::Log

EDIT2:事实上,这似乎是一个更一般的条件,即您可以通过模板间接访问一些内部私有类型,例如:

class F
{
  struct Foo
  {
    void bar() { }
  };
};

template <typename T>
struct ExposeInternal
{
  typedef T type;
};

int main(void)
{
  {
    // We've got Foo!
    ExposeInternal<F::Foo>::type t;
    t.bar();
  }
  {
    // Below fails
    F::Foo t;
    t.bar();
  }
}

编辑 3:好的 - 已经确认,这是一个报告的 GCC 错误,与 std::conditional 无关,尚未在 4.7 或 4.8 中修复。 http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47346

我将暂时保留这个问题。稍后将用上述内容关闭它。

【问题讨论】:

  • 请提供一个SSCCE(也许能找到更好的标题?!)
  • @stefan,它是 - 代码应该编译,为简洁起见,我省略了两个标题包含...
  • 仍然缺少#include &lt;string&gt; 和结构后的分号。这不是 SSCCE。
  • 另外,我没有得到你意想不到的东西。您能否还提供生成的输出和预期/想要的输出?
  • 当然需要&lt;string&gt;,它使用的是std::string&lt;iostream&gt;&lt;type_traits&gt; 在您的实现中引入 &lt;string&gt; 这一事实并不意味着它在每个实现中都如此。

标签: c++ c++11 gcc4.7


【解决方案1】:

我已经稍微修改了你的示例,所以现在使用我的 gcc 4.8.1 一切都按预期工作(预期)。

关于原始代码的几点说明:

  • 当您想测试Log 的可访问性(使用use_logger)时,主要误解use_loggerABC外部 类,D!它不能(按设计)访问除该类的 public 成员之外的任何内容!
  • 关于您的检查器的第二个方面:将类型 Log 传递给它,您将失去“上下文” - 即检查器不知道(并且它无法通过该设计实现它)“ 这种类型实际上是其他东西的嵌套类型吗?”
  • 最后use_logger 不正确:它总是0 重新解释为P*——没有其他可能性(解释方式)这段代码......背后的主要思想此类检查器将形成一组“匹配”函数,然后,在实例化时,编译器将通过 SFINAE 删除不合适的函数(并“回退”到通用 test(...) 重载)或从结果重载集中接受一些最合适的函数。您的 test(P*) 只是始终相关! -- 这就是为什么它实际上没有选择任何东西......

所以,这是我的代码:

#include <iostream>
#include <string>
#include <type_traits>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>

class Logger
{
    std::string _p;
public:
    Logger(std::string p): _p(p)
    { }

    void say(std::string message)
    {
        std::cout << _p << ' ' << message << std::endl;
    }
};

struct Log
{
    static Logger& log()
    {
        static Logger _def("Default: ");
        return _def;
    }
};

namespace details {
/// Helper class to check availability of a nested type \c Log
/// whithing \c T and it's static function \c log()
struct has_nested_logger_available_checker
{
    typedef char yes_type;
    typedef char (&no_type)[2];

    template <typename T>
    static no_type test(...);

    template <typename T>
    static yes_type test(
        typename std::add_pointer<
            decltype(std::is_same<decltype(T::Log::log()), Logger>::value, void())
        >::type
    );
};
}

/// Metafunction (type trait) to check is a nested type \c Log accessible
template <typename T>
struct has_nested_logger_available : std::is_same<
    decltype(details::has_nested_logger_available_checker::template test<T>(nullptr))
, details::has_nested_logger_available_checker::yes_type
>
{};

template <typename T>
struct access_nested_logger
{
    typedef typename T::Log type;
};

template <typename T>
struct logger_chooser : public boost::mpl::eval_if<
        has_nested_logger_available<T>
    , access_nested_logger<T>
    , boost::mpl::identity<::Log>
    >
{
};

class A
{
/// \attention I suppose original code has a typo here:
/// anything in a \c private section being inherited will be
/// \b inaccessible to a child with \c all kind of inheritance!
/// So if latter we want to use it from \c D, it \b must be at least
/// \c protected.
protected:
    struct Log
    {
        static Logger& log()
        {
            static Logger _def("A: ");
            return _def;
        }
    };

    /// \attention Checker and accessor \c MUST be a friend of this class.
    /// Cuz being called from \c A::say (which is actually a member, so it
    /// has full access to other members), it must have \b the same access
    /// as other (say) member(s)!!!
    friend struct details::has_nested_logger_available_checker;
    /// \todo Merge (actual) checker and "accessor" to the same class to
    /// reduce code to type... (a little)
    friend struct access_nested_logger<A>;

public:
    void say()
    {
        std::cout << "A: " << has_nested_logger_available<A>::value << std::endl;
        logger_chooser<A>::type::log().say("From A");
    }
};

class B
{
public:
    void say()
    {
        std::cout << "B: " << has_nested_logger_available<B>::value << std::endl;
        logger_chooser<B>::type::log().say("From B");
    }
};

class C : A
{
public:
    void say()
    {
        std::cout << "C: " << has_nested_logger_available<C>::value << std::endl;
        logger_chooser<C>::type::log().say("From C");
    }
};

/// With \c public inharitance, \c D can access \c public and/or \c protected
/// members of \c A. But not \c private !!!
class D : public A
{
public:
    /// \sa \c A
    friend struct details::has_nested_logger_available_checker;
    friend struct access_nested_logger<D>;

    void say()
    {
        std::cout << "D: " << has_nested_logger_available<D>::value << std::endl;
        logger_chooser<D>::type::log().say("From D");
    }
};

int main(void)
{
    {
        A i;
        i.say();
    }
    {
        B i;
        i.say();
    }
    {
        C i;
        i.say();
    }
    {
        D i;
        i.say();
    }
    return 0;
}

输出:

zaufi@gentop /work/tests $ g++ -std=c++11 -o so_log_test so_log_test.cc
zaufi@gentop /work/tests $ ./so_log_test
A: 1
A:  From A
B: 0
Default:  From B
C: 0
Default:  From C
D: 1
A:  From D

zaufi@gentop /work/tests $ g++ --version
g++ (Gentoo 4.8.1 p1.0, pie-0.5.6) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software
see the source for copying conditions.  There is NO
warranty
not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

【讨论】:

  • 您好,感谢您的回答。我知道我的测试有缺陷,只是还没来得及正确地修复它,然后才被这个错误击中。在这种情况下,“错误”实际上有助于使用简单的模板间接,我可以获得整个继承层次结构以使用相同的记录器或无法使用全局记录器!但是,这是一个我无法利用的错误.. :) 我对您采用的方法的唯一问题是我需要使用有问题的类型(AB 等)对测试进行模板化在可以在任何代码中的通用宏(例如ERRORWARNING)中...
  • 这不是问题 :) -- 您可能需要任何类型,它有自己的日志来提供 typedef A this_class; 并在宏中使用 this_class 别名...跨度>
猜你喜欢
  • 2021-06-29
  • 1970-01-01
  • 2013-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-30
相关资源
最近更新 更多