What will follow is not technically speaking a straight answer to your
question as it will still make use of the "friend" functionality
but it does not require modification of the tested entity itself
and I think it addesses the concern of breaking the encapsulation
mentioned in some of the other answers; it does though require
writing some boilerplate code.
The idea behind it is not mine and the implementation is
entirely based on a trick presented and explained by litb on his
blog(coupled with this Sutter's gotw for just a little bit
more context, at least for me) - in short CRTP, friends, ADL and pointers to members
(I must confess that to my dismay the ADL part I still don't
get it entirely, but I'm relentesly working in figuring it out 100%).
I tested it with gcc 4.6, clang 3.1 and VS2010 compilers and it
works perfectly.
/* test_tag.h */
#ifndef TEST_TAG_H_INCLUDED_
#define TEST_TAG_H_INCLUDED_
template <typename Tag, typename Tag::type M>
struct Rob
{
friend typename Tag::type get(Tag)
{
return M;
}
};
template <typename Tag, typename Member>
struct TagBase
{
typedef Member type;
friend type get(Tag);
};
#endif /* TEST_TAG_H_INCLUDED_ */
/* tested_class.h */
#ifndef TESTED_CLASS_H_INCLUDED_
#define TESTED_CLASS_H_INCLUDED_
#include <string>
struct tested_class
{
tested_class(int i, const char* descr) : i_(i), descr_(descr) { }
private:
int i_;
std::string descr_;
};
/* with or without the macros or even in a different file */
# ifdef TESTING_ENABLED
# include "test_tag.h"
struct tested_class_i : TagBase<tested_class_i, int tested_class::*> { };
struct tested_class_descr : TagBase<tested_class_descr, const std::string tested_class::*> { };
template struct Rob<tested_class_i, &tested_class::i_>;
template struct Rob<tested_class_descr, &tested_class::descr_>;
# endif
#endif /* TESTED_CLASS_H_INCLUDED_ */
/* test_access.cpp */
#include "tested_class.h"
#include <cstdlib>
#include <iostream>
#include <sstream>
#define STRINGIZE0(text) #text
#define STRINGIZE(text) STRINGIZE0(text)
int assert_handler(const char* expr, const char* theFile, int theLine)
{
std::stringstream message;
message << "Assertion " << expr << " failed in " << theFile << " at line " << theLine;
message << "." << std::endl;
std::cerr << message.str();
return 1;
}
#define ASSERT_HALT() exit(__LINE__)
#define ASSERT_EQUALS(lhs, rhs) ((void)(!((lhs) == (rhs)) && assert_handler(STRINGIZE((lhs == rhs)), __FILE__, __LINE__) && (ASSERT_HALT(), 1)))
int main()
{
tested_class foo(35, "Some foo!");
// the bind pointer to member by object reference could
// be further wrapped in some "nice" macros
std::cout << " Class guts: " << foo.*get(tested_class_i()) << " - " << foo.*get(tested_class_descr()) << std::endl;
ASSERT_EQUALS(35, foo.*get(tested_class_i()));
ASSERT_EQUALS("Some foo!", foo.*get(tested_class_descr()));
ASSERT_EQUALS(80, foo.*get(tested_class_i()));
return 0;
}