Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

fighting with move sematics and std::tuple

87 views
Skip to first unread message

Frank Bergemann

unread,
May 26, 2012, 1:35:10 PM5/26/12
to
Hi,
i am working on an exercise for move sematics and want to create a
'FunctionTracker', which logs enter/exit message for functions and
their input/output variables.
It works. But it uses some copying, which i want to shake off (if
possible).
Here's the FunctionTracker.h source file (i use gcc 4.7.0, ubuntu
linux 10.04.4, 32 bit):
-------------------------------------------------------------
/*
* FunctionTracker.h
*/
#ifndef FUNCTIONTRACKER_H_
#define FUNCTIONTRACKER_H_

#include <iostream>
#include <iomanip> // std::setw
#include <utility> // std::forward<>
#include <tuple> // std::tuple<>
#include <type_traits> // std::enable_if<>

/**
* classify input vs. output parameter
* to log for entering vs. exiting a procedure/function
*/
typedef enum
{
Input_c = 1,
Output_c = 2
} ParamType_t;

/**
* parameter type-independent base class
*/
struct _ArgCommon
{
char const * const _name;
ParamType_t const _type;

_ArgCommon(
char const * const name,
ParamType_t const type)
: _name(name),
_type(type)
{ };

#if 0
// TODO: want to get rid of the copy c'tor
_ArgCommon(_ArgCommon const &) = delete;
#else
_ArgCommon(
_ArgCommon const &rhs)
: _name(rhs._name),
_type(rhs._type)
{
std::cerr << "_ArgCommon copy c'tor" << std::endl;
};
#endif
_ArgCommon & operator=(_ArgCommon const &) = delete;
};

/**
* host class for parameter
* referencing to the variable
*/
template <typename T>
struct _Arg : public _ArgCommon
{
const T & _value;

_Arg(
char const * const name,
ParamType_t const type,
T const & value)
: _ArgCommon(name, type),
_value(value)
{ };

#if 0
// TODO: want to get rid of the copy c'tor
_Arg(_Arg const & rhs) = delete;
#else
_Arg(
_Arg const & rhs)
: _ArgCommon(rhs),
_value(rhs._value)
{ };
#endif

_Arg & operator=(_Arg const &) = delete;
};

/**
* ostream helper for _Arg<T>
*/
template <typename T>
std::ostream &operator<<(
std::ostream &out,
const _Arg<T> &arg)
{
out << arg._name << " => '" << arg._value << "'";
return out;
}

/**
* input parameter variant
*/
template <typename T>
struct InArg : public _Arg<T>
{
InArg(
char const * const name,
T const & value)
: _Arg<T>(name, Input_c, value)
{ };

#if 0
// TODO: want to get rid of the copy c'tor
InArg(InArg const & rhs) = delete;
#else
InArg(
InArg const & rhs)
: _Arg<T>(rhs)
{ };
#endif

InArg & operator=(InArg const &) = delete;
};

/**
* output parameter variant
*/
template <typename T>
struct OutArg : public _Arg<T>
{
OutArg(
char const * const name,
T const & value)
: _Arg<T>(name, Output_c, value)
{ };

#if 0
// TODO: want to get rid of the copy c'tor
OutArg(OutArg const & rhs) = delete;
#else
OutArg(
OutArg const & rhs)
: _Arg<T>(rhs)
{ };
#endif

OutArg & operator=(OutArg const &) = delete;
};


template <typename T>
const InArg<T> MakeInArg(
char const * const name,
T & value)
{
std::cerr << "MakeInArg: invoked" << std::endl;
return InArg<T>(name, value);
};

template <typename T>
const OutArg<T> MakeOutArg(
char const * const name,
T & value)
{
return OutArg<T>(name, value);
};

// variadic template type list transformation
template <typename ...Types>
struct _ArgTuple
{
typedef typename std::tuple<const _Arg<Types>...> Type;
};

/**
* recursive tuple ostream helper template
*/

/**
* recursion
*/
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
PrintTuple(const std::tuple<Tp...>& t)
{
std::cerr << std::get<I>(t) << ", ";
PrintTuple<I + 1, Tp...>(t);
}

/**
* recursion termination
*/
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
PrintTuple(const std::tuple<Tp...>& t)
{ }

/**
* shared recursion level
* (does not support threads!)
*/
struct _FunctionTrackerShared {
static int _level;
};

/**
* FunctionTracker implementation
* but not used directly
* 'FunctionTracker' is used instead
*/
template <typename PARAMS>
class _FunctionTrackerImpl
{
private:
char const * const _funcName;

const PARAMS _params;

protected:
_FunctionTrackerImpl(
char const* const funcName,
PARAMS && params)
: _funcName(funcName?funcName:"<unknown>"),
_params(std::forward<PARAMS>(params))
{
if (_FunctionTrackerShared::_level > 0) {
std::cerr << std::setw(_FunctionTrackerShared::_level) << ' ';
}
std::cerr << _funcName << ": enter with ";
PrintInput(_params);
std::cerr << std::endl;
++_FunctionTrackerShared::_level;
}

#if 0
_FunctionTrackerImpl(const _FunctionTrackerImpl &) = delete;
#else
_FunctionTrackerImpl(const _FunctionTrackerImpl & rhs)
: _funcName(rhs._funcName),
_params(rhs._params)
{
std::cerr << "!!! _FunctionTrackerImpl copy c'tor !!!" <<
std::endl;
}
#endif

~_FunctionTrackerImpl()
{
--_FunctionTrackerShared::_level;
if (_FunctionTrackerShared::_level > 0) {
std::cerr << std::setw(_FunctionTrackerShared::_level) << ' ';
}
std::cerr << _funcName << ": exit with ";
PrintOutput(_params);
std::cerr << std::endl;
}

void PrintInput(
const PARAMS & params)
{
std::cerr << "input: ";
PrintTuple(Input_c, params);
}

void PrintOutput(
const PARAMS & params)
{
std::cerr << "output: ";
PrintTuple(Output_c, params);
}

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
PrintTuple(ParamType_t const & paramType, const std::tuple<Tp...>&
t)
{ }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
PrintTuple(ParamType_t const & paramType, const std::tuple<Tp...>&
t)
{
const _ArgCommon& arg = std::get<I>(t);
if (paramType == arg._type) {
std::cerr << std::get<I>(t) << ", ";
}
PrintTuple<I + 1, Tp...>(paramType, t);
}

};

/**
* FunctionTracker factory supporting variable arguments
*/
template <typename... Types>
class FunctionTracker : public _FunctionTrackerImpl< typename
_ArgTuple<Types...>::Type >
{
private:
typedef _FunctionTrackerImpl< typename _ArgTuple<Types...>::Type >
BaseType;

typedef typename _ArgTuple<Types...>::Type PARAMS;

FunctionTracker(
char const * const funcName,
PARAMS && params)
: _FunctionTrackerImpl<PARAMS>(funcName,
std::forward<PARAMS>(params))
{ };

public:
FunctionTracker(const FunctionTracker & rhs)
: BaseType(rhs)
{ };

template <typename... Args>
static FunctionTracker make(
char const * const funcName,
Args... args)
{
std::cerr << "FunctionTracker::make(...): create tuple" <<
std::endl;
auto forTest = std::make_tuple(std::forward<Args>(args)...);
std::cerr << "FunctionTracker::make(...): create and return
FunctionTracker" << std::endl;
return FunctionTracker<Types...>(funcName,
std::make_tuple(std::move(args)...));
};
};


#endif /*FUNCTIONTRACKER_H_*/
-------------------------------------------------------------
And here a little test main.cpp:
-------------------------------------------------------------
/*
* main.cpp
*
* Created on: Oct 30, 2011
* Author: frank
*/

#include <utility> // std::move<>

#include "FunctionTracker.h"

int
main(
int argc,
char ** argv)
{
int x = 5;

auto funcTracker = FunctionTracker<
std::remove_reference<decltype(x)>::type
>::make(
__FUNCTION__,
MakeInArg("x", x));

return 0;
}
-------------------------------------------------------------
And this is the output:
-------------------------------------------------------------
frank@frank-desktop:~/workspace/FunctionTracker/Debug$ ./
FunctionTracker
MakeInArg: invoked
FunctionTracker::make(...): create tuple
_ArgCommon copy c'tor
FunctionTracker::make(...): create and return FunctionTracker
_ArgCommon copy c'tor
_ArgCommon copy c'tor
_ArgCommon copy c'tor
main: enter with input: x => '5',
main: exit with output:
-------------------------------------------------------------

See, there is a copy c'tor invoked #1 time for just creating the
tuple.
(Added in the FunctionTracker::make(...) just for test.
And in know, that i shouldn't apply std::forward and std::move both
to the same data - just for test.
Btw, i tries both for the 'FunctionTracker': std::move and
std::forward - no difference.)

And there is even copied #3 times for creating the real
'FunctionTracker' object.

Can you pls. help?

- Many thanks!

best regards,
Frank


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Frank Bergemann

unread,
May 27, 2012, 3:32:03 AM5/27/12
to
I added move constructors and much more debugging.
Here's the new FunctionTracker.h source file:
-------------------------------------------------------------
/*
* FunctionTracker.h
*
* Created on: Oct 30, 2011
* Author: frank
*/
#ifndef FUNCTIONTRACKER_H_
#define FUNCTIONTRACKER_H_

#include <string>
#include <iostream>
#include <iomanip> // std::setw
#include <utility> // std::forward<>
#include <tuple> // std::tuple<>
#include <type_traits> // std::enable_if<>

struct EnterExit
{
std::string _name;

EnterExit(std::string const & name)
:_name(name)
{
std::cerr << _name << ": enter" << std::endl;
};

~EnterExit()
{
std::cerr << _name << ": exit" << std::endl;
};
};


/**
* classify input vs. output parameter
* to log for entering vs. exiting a procedure/function
*/
typedef enum
{
Input_c = 1,
Output_c = 2
} ParamType_t;


/**
* parameter type-independent base class
*/
struct _ArgCommon
{
char const * const _name;
ParamType_t const _type;

_ArgCommon(
char const * const name,
ParamType_t const type)
: _name(name),
_type(type)
{
std::cerr << "_ArgCommon: c'tor" << std::endl;
};

_ArgCommon(_ArgCommon&& rhs)
: _name(rhs._name),
_type(rhs._type)
{
std::cerr << "_ArgCommon: move c'tor" << std::endl;
};

#if 0
// TODO: want to get rid of the copy c'tor
_ArgCommon(_ArgCommon const &) = delete;
#else
_ArgCommon(
_ArgCommon const &rhs)
: _name(rhs._name),
_type(rhs._type)
{
std::cerr << "_ArgCommon copy c'tor" << std::endl;
};
#endif
_ArgCommon & operator=(_ArgCommon const &) = delete;
};

/**
* host class for parameter_name << ": enter" <<
* referencing to the variable
*/
template <typename T>
struct _Arg : public _ArgCommon
{
const T & _value;

_Arg(
char const * const name,
ParamType_t const type,
T const & value)
: _ArgCommon(name, type),
_value(value)
{
std::cerr << "_Arg: c'tor" << std::endl;
};


_Arg(_Arg&& rhs)
: _ArgCommon(std::move(rhs)),
_value(rhs._value)
{
std::cerr << "_Arg: move c'tor" << std::endl;
};


#if 0
// TODO: want to get rid of the copy c'tor
_Arg(_Arg const & rhs) = delete;
#else
_Arg(
_Arg const & rhs)
: _ArgCommon(rhs),
_value(rhs._value)
{
std::cerr << "_Arg: copy c'tor" << std::endl;
};
#endif

_Arg & operator=(_Arg const &) = delete;
};

/**
* ostream helper for _Arg<T>
*/
template <typename T>
std::ostream &operator<<(
std::ostream &out,
const _Arg<T> &arg)
{
out << arg._name << " => '" << arg._value << "'";
return out;
}

/**
* input parameter variant
*/
template <typename T>
struct InArg : public _Arg<T>
{
InArg(
char const * const name,
T const & value)
: _Arg<T>(name, Input_c, value)
{
std::cerr << "InArg: c'tor" << std::endl;
};

// move constructor
InArg(InArg&& rhs)
: _Arg<T>(std::move(rhs))
{
std::cerr << "InArg: move c'tor" << std::endl;
};

#if 0
// TODO: want to get rid of the copy c'tor
InArg(InArg const & rhs) = delete;
#else
InArg(
InArg const & rhs)
: _Arg<T>(rhs)
{
std::cerr << "InArg: copy c'tor" << std::endl;
};
#endif

InArg & operator=(InArg const &) = delete;
};

/**
* output parameter variant
*/
template <typename T>
struct OutArg : public _Arg<T>
{
OutArg(
char const * const name,
T const & value)
: _Arg<T>(name, Output_c, value)
{
std::cerr << "OutArg: c'tor" << std::endl;
};

// move constructor
OutArg(OutArg&& rhs)
: _Arg<T>(std::move(rhs))
{
std::cerr << "OutArg: move c'tor" << std::endl;
};

#if 0
// TODO: want to get rid of the copy c'tor
OutArg(OutArg const & rhs) = delete;
#else
OutArg(
OutArg const & rhs)
: _Arg<T>(rhs)
{
std::cerr << "OutArg: copy c'tor" << std::endl;
};
#endif

OutArg & operator=(OutArg const &) = delete;
};


template <typename T>
InArg<T> MakeInArg(
char const * const name,
T & value)
{
return InArg<T>(name, value);
};

template <typename T>
OutArg<T> MakeOutArg(
char const * const name,
T & value)
{
return OutArg<T>(name, value);
};

// variadic template type list transformation
template <typename ...Types>
struct _ArgTuple
{
typedef typename std::tuple<const _Arg<Types>...> Type;
};

/**
* shared recursion level
* (does not support threads!)
*/
struct _FunctionTrackerShared {
static int _level;
};

/**
* FunctionTracker implementation
* but not used directly
* 'FunctionTracker' is used instead
*/
template <typename PARAMS>
class _FunctionTrackerImpl
{
private:
EnterExit _dummy;

char const * const _funcName;

const PARAMS _params;

protected:
_FunctionTrackerImpl(
char const* const funcName,
PARAMS && params)
: _dummy("EnterExit(FunctionTrackerImpl: c'tor MIL"),
_funcName(funcName?funcName:"<unknown>"),
_params(std::move(params))
{
EnterExit dummy("EnterExit(FunctionTrackerImpl: c'tor)");
if (_FunctionTrackerShared::_level > 0) {
std::cerr << std::setw(_FunctionTrackerShared::_level) << ' ';
}
std::cerr << _funcName << ": enter with ";
PrintInput(_params);
std::cerr << std::endl;
++_FunctionTrackerShared::_level;
}

#if 0
_FunctionTrackerImpl(const _FunctionTrackerImpl &) = delete;
#else
_FunctionTrackerImpl(const _FunctionTrackerImpl & rhs)
: _dummy("EnterExit(FunctionTrackerImpl: copy c'tor MIL"),
EnterExit _enterExit;

typedef _FunctionTrackerImpl< typename _ArgTuple<Types...>::Type >
BaseType;

typedef typename _ArgTuple<Types...>::Type PARAMS;

FunctionTracker(
char const * const funcName,
PARAMS && params)
: BaseType(funcName, std::move(params)),
_enterExit("EnterExit(FunctionTracker: c'tor MIL)")
{
EnterExit dummy("EnterExit(FunctionTracker: c'tor)");
};

public:
FunctionTracker(const FunctionTracker & rhs)
: BaseType(rhs),
_enterExit("EnterExit(FunctionTracker: copy c'tor MIL)")
{
EnterExit dummy("EnterExit(FunctionTracker: copy c'tor)");
};

FunctionTracker(FunctionTracker && rhs)
: BaseType(std::move(rhs)),
_enterExit("EnterExit(FunctionTracker: move c'tor MIL)")
{
EnterExit dummy("EnterExit(FunctionTracker: move c'tor)");
};

template <typename... Args>
static FunctionTracker make(
char const * const funcName,
Args... args)
{
EnterExit dummy("EnterExit(FunctionTracker::make)");
return FunctionTracker<Types...>(funcName,
std::make_tuple(std::move(args)...));
};
};


#endif /*FUNCTIONTRACKER_H_*/
-------------------------------------------------------------
And here the test main.cpp:
-------------------------------------------------------------
/*
* main.cpp
*
* Created on: Oct 30, 2011
* Author: frank
*/

#include <utility> // std::move<>
#include <functional>

#include "FunctionTracker.h"


int
main(
int argc,
char ** argv)
{
int x = 5;

std::cerr << "### main: create tuple" << std::endl;
std::tuple<InArg<int> > forTest = std::make_tuple(MakeInArg("x", x));

std::cerr << "### main: create FunctionTracker" << std::endl;
auto funcTracker = FunctionTracker<
std::remove_reference<decltype(x)>::type
>::make(
__FUNCTION__,
MakeInArg("x", x));

return 0;
}
-------------------------------------------------------------
And this is the output:
-------------------------------------------------------------
frank@frank-desktop:~/workspace/FunctionTracker/Debug$ ./
FunctionTracker
### main: create tuple
_ArgCommon: c'tor
_Arg: c'tor
InArg: c'tor
_ArgCommon: move c'tor
_Arg: move c'tor
InArg: move c'tor
### main: create FunctionTracker
_ArgCommon: c'tor
_Arg: c'tor
InArg: c'tor
EnterExit(FunctionTracker::make): enter
_ArgCommon: move c'tor
_Arg: move c'tor
InArg: move c'tor
_ArgCommon: move c'tor
_Arg: move c'tor
EnterExit(FunctionTrackerImpl: c'tor MIL: enter
_ArgCommon copy c'tor
_Arg: copy c'tor
EnterExit(FunctionTrackerImpl: c'tor): enter
main: enter with input: x => '5',
EnterExit(FunctionTrackerImpl: c'tor): exit
EnterExit(FunctionTracker: c'tor MIL): enter
EnterExit(FunctionTracker: c'tor): enter
EnterExit(FunctionTracker: c'tor): exit
EnterExit(FunctionTracker::make): exit
EnterExit(FunctionTracker: c'tor MIL): exit
main: exit with output:
EnterExit(FunctionTrackerImpl: c'tor MIL: exit
-------------------------------------------------------------
I think first there needs to be avoided the use of the copy c'tor of
FunctionTracker
(and so FunctionTrackerImpl).
But i can't see where this copying comes from(?!)

Daniel Krügler

unread,
May 27, 2012, 5:14:13 PM5/27/12
to
Am 27.05.2012 09:32, schrieb Frank Bergemann:

[..]

> struct _ArgCommon

You should be aware, that according to [global.names]:

"Each name that [..] begins with an underscore followed by an
uppercase letter (2.12) is reserved to the implementation for any
use."

Do not introduce such names, unless you are a library implementation.
You may get unexpected errors that are hard to track down.

> template<typename ...Types>
> struct _ArgTuple
> {
> typedef typename std::tuple<const _Arg<Types>...> Type;
> };

There is a lot of code, but above member type definition is at least
one source of the trouble, see below.

> struct _FunctionTrackerShared {
> static int _level;
> };

Let me add that without a definition of FunctionTrackerShared::level
your code is ill-formed, no diagnostic required.

> int
> main(
> int argc,
> char ** argv)
> {
> int x = 5;
>
> std::cerr<< "### main: create tuple"<< std::endl;
> std::tuple<InArg<int> > forTest = std::make_tuple(MakeInArg("x", x));
>
> std::cerr<< "### main: create FunctionTracker"<< std::endl;
> auto funcTracker = FunctionTracker<
> std::remove_reference<decltype(x)>::type
> >::make(
> __FUNCTION__,

The C++11 standard does not specify an entity or macro __FUNCTION__. I
assume you meant __func__ instead.

Back to your problem again: To produce a simplified model of what is
going one we invent the following simple function template:

template <typename... Args>
typename ArgTuple<int>::Type my_make(Args... args)
{
return std::make_tuple(std::move(args)...);
}

and call it like this:

int main()
{
int x = 5;
my_make(MakeInArg("x", x));
}

This would already be ill-formed, when you have declared all your copy
constructors as deleted (as indicated by your preprocessor
defines). Or in other words: This already requires a copy constructor
for ArgTuple<int>::Type. The reason is very simply:
ArgTuple<int>::Type is - according to your definition - a typedef for
std::tuple<const _Arg<int>>. It is easy to understand that *any*
std::tuple<const X> cannot be a move-only type for some object type X,
simply because the move-constructor would be ill-formed when there is
a const data member. Compare this with the following
copy-initialization:

#include <utility>

struct MO {
MO() = default;
MO(MO&&) = default;
};

const MO mo;
MO mo2 = std::move(mo);

HTH & Greetings from Bremen,

Daniel Krügler

Frank Bergemann

unread,
May 28, 2012, 1:13:26 PM5/28/12
to
Hi Daniel,

thanks for all the hints!
The most relevant was this:

>
> > template<typename ...Types>
> > struct _ArgTuple
> > {
> > typedef typename std::tuple<const _Arg<Types>...> Type;
> > };
>
> There is a lot of code, but above member type definition is at least
> one source of the trouble, see below.
>
> [...]
>
> It is easy to understand that *any*
> std::tuple<const X> cannot be a move-only type for some object type X,
> simply because the move-constructor would be ill-formed when there is
> a const data member.

So here's the updated version:

FunctionTracker.h:
----------------------------------------------------------
/*
* FunctionTracker.h
*
* Created on: Oct 30, 2011
* Author: frank
*/
#ifndef FUNCTIONTRACKER_H_
#define FUNCTIONTRACKER_H_

#include <string>
#include <iostream>
#include <iomanip> // std::setw
#include <utility> // std::forward<>
#include <tuple> // std::tuple<>
#include <type_traits> // std::enable_if<>
#include <stdexcept> // std::runtime_error

namespace FT // FunctionTracker
{

/**
* parameter type-independent base class
*/
struct ArgCommon
{
/**
* classify input vs. output parameter
* to log for entering vs. exiting a procedure/function
*/
typedef enum
{
Input_c = 1,
Output_c = 2
} ParamType_t;

char const * const name;
ParamType_t const type;

ArgCommon(
char const * const name,
ParamType_t const type)
: name(name),
type(type)
{ };

ArgCommon(ArgCommon&& rhs)
: name(rhs.name),
type(rhs.type)
{ };

ArgCommon(
ArgCommon const &rhs)
: name(rhs.name),
type(rhs.type)
{
throw std::runtime_error("ArgCommon: invalid invocation of copy
c'tor");
};

ArgCommon & operator=(ArgCommon const &) = delete;
};

/**
* host class for parameter_name << ": enter" <<
* referencing to the variable
*/
template <typename T>
struct Arg : public ArgCommon
{
const T & value;

Arg(
char const * const name,
ArgCommon::ParamType_t const type,
T const & value)
: ArgCommon(name, type),
value(value)
{ };


Arg(Arg&& rhs)
: ArgCommon(std::move(rhs)),
value(rhs.value)
{ };


Arg(
Arg const & rhs)
: ArgCommon(rhs), // will throw
value(rhs.value)
{ };

Arg & operator=(Arg const &) = delete;
};

/**
* ostream helper for Arg<T>
*/
template <typename T>
std::ostream &operator<<(
std::ostream &out,
const Arg<T> &arg)
{
out << arg.name << " => '" << arg.value << "'";
return out;
}

template <typename T>
Arg<T> MakeInArg(
char const * const name,
T & value)
{
return Arg<T>(name, ArgCommon::Input_c, value);
};

template <typename T>
Arg<T> MakeOutArg(
char const * const name,
T & value)
{
return Arg<T>(name, ArgCommon::Output_c, value);
};

// variadic template type list transformation
template <typename ...Types>
struct ArgTuple
{
typedef typename std::tuple<Arg<Types>...> Type;
};

/**
* shared recursion level
* (does not support threads!)
*/
struct FunctionTrackerShared {
static int level;
};

/**
* FunctionTracker implementation
* but not used directly
* 'FunctionTracker' is used instead
*/
template <typename PARAMS>
class FunctionTrackerImpl
{
private:
char const * const funcName;
const PARAMS params;

protected:
FunctionTrackerImpl(
char const* const funcName,
PARAMS && params)
: funcName(funcName?funcName:"<unknown>"),
params(std::move(params))
{
if (FunctionTrackerShared::level > 0) {
std::cerr << std::setw(FunctionTrackerShared::level) << ' ';
}
std::cerr << funcName << ": enter with input: ";
PrintTuple(ArgCommon::Input_c, params);
std::cerr << std::endl;
++FunctionTrackerShared::level;
}

FunctionTrackerImpl(const FunctionTrackerImpl & rhs)
: funcName(rhs.funcName),
params(rhs.params)
{
throw std::runtime_error("FunctionTrackerImpl: invalid invocation
of copy c'tor");
}

FunctionTrackerImpl & operator=(const FunctionTrackerImpl &rhs) =
delete;

~FunctionTrackerImpl()
{
--FunctionTrackerShared::level;
if (FunctionTrackerShared::level > 0) {
std::cerr << std::setw(FunctionTrackerShared::level) << ' ';
}
std::cerr << funcName << ": exit with output: ";
PrintTuple(ArgCommon::Output_c, params);
std::cerr << std::endl;
}

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
PrintTuple(ArgCommon::ParamType_t const & paramType, const
std::tuple<Tp...>& t)
{ }

// TODO: turn dynamic checks into compile time evaluation
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
PrintTuple(ArgCommon::ParamType_t const & paramType, const
std::tuple<Tp...>& t)
{
const ArgCommon& arg = std::get<I>(t);
if (paramType == arg.type) {
std::cerr << std::get<I>(t);
if (I+1!=sizeof...(Tp)) std::cerr << ", ";
}
PrintTuple<I + 1, Tp...>(paramType, t);
}
};

/**
* FunctionTracker factory supporting variable arguments
*/
template <typename... Types>
class FunctionTracker : public FunctionTrackerImpl< typename
ArgTuple<Types...>::Type >
{
private:
typedef FunctionTrackerImpl< typename ArgTuple<Types...>::Type >
BaseType;

typedef typename ArgTuple<Types...>::Type PARAMS;

FunctionTracker(
char const * const funcName,
PARAMS && params)
: BaseType(funcName, std::move(params))
{ };

public:
// cannot delete copy c'tor
// because FunctionTracker::Make formally requires it
// though doesn't actually use if (RVO)
FunctionTracker(const FunctionTracker & rhs)
: BaseType(rhs) // will throw
{ };

FunctionTracker & operator=(const FunctionTracker &rhs) = delete;

template <typename... Args>
static FunctionTracker make(
char const * const funcName,
Args... args)
{
return FunctionTracker<Types...>(funcName,
std::make_tuple(std::move(args)...));
};
};

} // namespace FT

#endif /*FUNCTIONTRACKER_H_*/
----------------------------------------------------------
FunctionTracker.cpp:
----------------------------------------------------------
/*
* FunctionTracker.cpp
*
* Created on: Oct 30, 2011
* Author: frank
*/
#include "FunctionTracker.h"

int FT::FunctionTrackerShared::level = 0;
----------------------------------------------------------
main.cpp:
----------------------------------------------------------
/*
* main.cpp
*
* Created on: Oct 30, 2011
* Author: frank
*/
#include <utility>

#include "FunctionTracker.h"

int
main(
int argc,
char ** argv)
{
int x = 5;

std::cerr << "### main: create tuple" << std::endl;
std::tuple<FT::Arg<int> > forTest =
std::make_tuple(FT::MakeInArg("x", x));

std::cerr << "### main: create FunctionTracker" << std::endl;
auto funcTracker = FT::FunctionTracker<
std::remove_reference<decltype(x)>::type
>::make(
__FUNCTION__,
FT::MakeInArg("x", x));

return 0;
}
----------------------------------------------------------
output:
----------------------------------------------------------
frank@frank-desktop:~/workspace/FunctionTracker/Debug$ ./
FunctionTracker
### main: create tuple
### main: create FunctionTracker
main: enter with input: x => '5'
main: exit with output:
----------------------------------------------------------

The reason for need of copy constructor seems to be the
FunctionTracker::make(...).
it uses:
return FunctionTracker<Types...>(funcName,
std::make_tuple(std::move(args)...));
I guess this "formally" requires the copy c'tor(?)
(see my comment:
// cannot delete copy c'tor
// because FunctionTracker::Make formally requires it
// though doesn't actually use if (RVO)
)
That's why i cannot use "= delete" for those.
However, as the copy c'tor (now) are not used actually, i implemented
them, but raise a runtime exception in case they WOULD be used.
I want the Function::Tracker::make(...) to serve for type-
transformation.
Is there a better way to resolve this conflict?

regards,
Frank

Frank Bergemann

unread,
May 28, 2012, 4:43:04 PM5/28/12
to
{ Although this post contains a lot of code and no explanatory
content, it is accepted because it appears to conclude this thread.
When following up, please keep in mind that according to the group's
website, articles containing excessive code are rejected -mod }

This one is much better.

FunctionTracker.h:
----------------------------------------------------------
/*
* FunctionTracker.h
*
* Created on: May 28, 2012
* Author: frank
*/
#ifndef FUNCTIONTRACKER_H_
#define FUNCTIONTRACKER_H_

#include <string>
#include <iostream>
#include <iomanip> // std::setw
#include <utility> // std::forward<>
#include <tuple> // std::tuple<>
#include <type_traits> // std::enable_if<>
#include <stdexcept> // std::runtime_error

namespace FT // FunctionTracker
{

/**
* parameter type-independent base class
*/
struct ArgCommon
{

/**
* classify input vs. output parameter
* to log for entering vs. exiting a procedure/function
*/
typedef enum
{
Input_c = 1,
Output_c = 2
} ParamType_t;

char const * const name;
ParamType_t const type;

ArgCommon(
char const * const name,
ParamType_t const type)
: name(name),
type(type)
{ };

ArgCommon(ArgCommon&& rhs)
: name(rhs.name),
type(rhs.type)
{ };

ArgCommon(
ArgCommon const &rhs)
: name(rhs.name),
type(rhs.type)
{
throw std::runtime_error("ArgCommon: invalid invocation of copy
c'tor");
};

ArgCommon & operator=(ArgCommon const &) = delete;
};

/**
* host class for parameter_name << ": enter" <<
* referencing to the variable
*/
template <typename T>
struct Arg : public ArgCommon
{
typedef T Type;

typedef typename std::remove_reference<T>::type NoRef;

const NoRef & value;

Arg(
char const * const name,
ArgCommon::ParamType_t const type,
T const & value)
: ArgCommon(name, type),
value(value)
{ };


Arg(Arg&& rhs)
: ArgCommon(std::move(rhs)),
value(rhs.value)
{ };


Arg(
Arg const & rhs)
: ArgCommon(rhs), // will throw
value(rhs.value)
{ };

Arg & operator=(Arg const &) = delete;
};


/**
* ostream helper for Arg<T>
*/
template <typename T>
std::ostream &operator<<(
std::ostream &out,
const Arg<T> &arg)
{
out << arg.name << " => '" << arg.value << "'";
return out;
}

template <typename T>
Arg<T> MakeInArg(
char const * const name,
T & value)
{
return Arg<T>(name, ArgCommon::Input_c, value);
};

template <typename T>
Arg<T> MakeOutArg(
char const * const name,
T & value)
{
return Arg<T>(name, ArgCommon::Output_c, value);
};


// variadic template type list (get base types from IN/OUT Args)
template <typename ...Types>
struct TypeTuple
{
typedef typename std::tuple<typename Arg<Types>::Type...> Type;
};

/**
* shared recursion level
* (does not support threads!)
*/
struct FunctionTrackerShared {
static int level;
};

/**
* FunctionTracker implementation
* but not used directly
* 'FunctionTracker' is used instead
*/
template <typename PARAMS>
class FunctionTracker
{
private:
char const * const funcName;
const PARAMS params;

public:
FunctionTracker(
char const* const funcName,
PARAMS && params)
: funcName(funcName?funcName:"<unknown>"),
params(std::move(params))
{
if (FunctionTrackerShared::level > 0) {
std::cerr << std::setw(FunctionTrackerShared::level) << ' ';
}
std::cerr << funcName << ": enter with input: ";
PrintTuple(ArgCommon::Input_c, params);
std::cerr << std::endl;
++FunctionTrackerShared::level;
}

FunctionTracker(const FunctionTracker & rhs)
: funcName(rhs.funcName),
params(rhs.params)
{
throw std::runtime_error("FunctionTrackerImpl: invalid invocation
of copy c'tor");
}

FunctionTracker & operator=(const FunctionTracker &rhs) = delete;

~FunctionTracker()
{
--FunctionTrackerShared::level;
if (FunctionTrackerShared::level > 0) {
std::cerr << std::setw(FunctionTrackerShared::level) << ' ';
}
std::cerr << funcName << ": exit with output: ";
PrintTuple(ArgCommon::Output_c, params);
std::cerr << std::endl;
}

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
PrintTuple(ArgCommon::ParamType_t const & paramType, const
std::tuple<Tp...>& t)
{ }

// TODO: turn dynamic checks into compile time evaluation
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
PrintTuple(ArgCommon::ParamType_t const & paramType, const
std::tuple<Tp...>& t)
{
const ArgCommon& arg = std::get<I>(t);
if (paramType == arg.type) {
std::cerr << std::get<I>(t);
if (I+1!=sizeof...(Tp)) std::cerr << ", ";
}
PrintTuple<I + 1, Tp...>(paramType, t);
}
};

template <typename... Types>
FunctionTracker<typename TypeTuple<Types...>::Type> make(
char const * const funcName,
Types... args)
{
return FunctionTracker<typename TypeTuple<Types...>::Type>(funcName,
std::make_tuple(std::move(args)...));
};

} // namespace FT

#endif /*FUNCTIONTRACKER_H_*/
----------------------------------------------------------
FunctionTracker.cpp:
----------------------------------------------------------
/*
* FunctionTracker.cpp
*
* Created on: May 28, 2012
* Author: frank
*/
#include "FunctionTracker.h"

int FT::FunctionTrackerShared::level = 0;
----------------------------------------------------------
main.cpp:
----------------------------------------------------------
/*
* main.cpp
*
* Created on: Oct 30, 2011
* Author: frank
*/
#include <utility>

#include "FunctionTracker.h"

int
main(
int argc,
char ** argv)
{
int x = 5;

auto funcTracker = FT::make(
__func__,
FT::MakeInArg("x", x));

return 0;
}
----------------------------------------------------------
output:
----------------------------------------------------------
frank@frank-desktop:~/workspace/FunctionTracker/Debug$ ./
FunctionTracker
main: enter with input: x => '5'
main: exit with output:
----------------------------------------------------------

rgds,
Frank

Daniel Krügler

unread,
May 29, 2012, 3:53:53 PM5/29/12
to
On 2012-05-28 19:13, Frank Bergemann wrote:
> So here's the updated version:

[..]

> The reason for need of copy constructor seems to be the
> FunctionTracker::make(...).
> it uses:
> return FunctionTracker<Types...>(funcName,
> std::make_tuple(std::move(args)...));
> I guess this "formally" requires the copy c'tor(?)
> (see my comment:
> // cannot delete copy c'tor
> // because FunctionTracker::Make formally requires it
> // though doesn't actually use if (RVO)
> )
> That's why i cannot use "= delete" for those.
> However, as the copy c'tor (now) are not used actually, i implemented
> them, but raise a runtime exception in case they WOULD be used.
> I want the Function::Tracker::make(...) to serve for type-
> transformation.
> Is there a better way to resolve this conflict?

If you want to exclude copy semantics and restrict to move semantics it
would be much better to mark all copy constructors as deleted
(explicitly or implicitly) and to define corresponding move
constructors. In C++11 a move-only type can be returned from functions
and can be provided as function parameter.

Further, I don't see any good reasons for the const data members in
FunctionTrackerImpl: If you want to create a move-only type from this,
you should better remove the top-level const specifier from the member
declarations.

HTH & Greetings from Bremen,

Daniel Krügler


0 new messages