D
dizzy
Hi
I've started lately to run more often than not into situations where I need
a function to perform several duties, but the duties to perform (although
decided at the point of call, at compile time) are not controlled by the
signature (the arguments are the same) so overloading normally won't help
(unless abused like new-expression does with std::nothrow). There is also a
need to remove any redudant or duplicated code. I will take a "real world"
example, supose I want to "beautify" and extend/wrap the mkdir(2) POSIX
call. Requirements (acording to the code that will use it):
1. beautify:
1.1 it will take not only char const* for pathname but also std::string
const& (most likely to be used alot more oftenthan the char const* version)
1.2 also, mkdir(2) takes 2 arguments, a path and a second argument which
represents the privileges to be created with for that directory;
beautification asks us to make this second argument a default argument of
value 0755
2. extend/changed semantics:
2.1 there must be a nothrow version that returns with bool if it succeeded
2.2 there must be a throw version that throws in case of error
2.3 there must be a version where the code should NOT consider an error if
mkdir(2) returns -1 and errno == EEXIST and there is a directory already
2.4 there must be a version to be used when the caller does not care about
any possible failure (best effort version, this version is needed because
in order to properly solve requirement 2.3 one needs to use an expensive
(l)stat because EEXIST is also returned in case of non directories existing
at the same path and as such, some users may just want a best effort mkdir
without the cost of doing stat in case of EEXIST failure)
Sounds simple and straightforward
(the char const* taking versions are not showed for brevity although the
real code has the std::string const& taking versions be just inline
relayers to the char const* versions using .c_str() on the received path)
bool mkdir_nothrow(std::string const& path, mode_t privs = 0755) {
struct stat stbuf;
return !::mkdir(path.c_str(), privs) || (errno == EEXIST
&& !::lstat(path.c_str(), &stbuf) && S_ISDIR(stbuf.st_mode));
}
void mkdir(std::string const& path, mode_t privs = 0755) {
if (!mkdir_nothrow(path, privs))
throw std::runtime_error("mkdir");
}
The above code should satisfy all requirements except 2.4. To satisfy 2.4
one may argue the user may just directly call mkdir(2) which does just
that, but mkdir(2) does not satisfy the beautification requirements.
Because I want "syntax compression" (that is short, expressive syntax) for
all these mkdir cases I thought a better solution is needed, something
like:
enum {
mkdir_nothrow = 0,
mkdir_throw = 1,
mkdir_noexists = 0,
mkdir_exists = 2
}
template<unsigned Options>
typename if_<Options & mkdir_throw, void, bool>::type
mkdir(std::string const& path, mode_t privs = 0755) {
bool res = !::mkdir(path.c_str(), privs);
if (Options & mkdir_exists) {
struct stat stbuf;
res ||= errno == EEXIST && !::lstat(path.c_str(), &stbuf) &&
S_ISDIR(stbuf.st_mode);
}
if ((Options & mkdir_throw) && !res)
throw std::runtime_error("mkdir");
return typename if_<Options & mkdir_throw, void, bool>::type(res);
}
Well, maybe the implementation could be better (please provide better
examples, I'm pretty new into this kind of coding style), I realise I could
use some template specializations instead of relying on the compiler
optimizer with "if (Options & various)" being optimized out based on
compile time known values and also the last line/conversion. But I am more
interested to know if such way to solve things is fairly common, I have a
strong feeling I'm missing something big that I had to write all this stuff
just to have flexible behaviour control over a simple function like that.
The end result is pretty nice tho:
mkdir<mkdir_nothrow | mkdir_exists>(path);
or mkdir<mkdir_throw | mkdir_noexists>(path, 0700);
How do you people solve similar issues?
I've started lately to run more often than not into situations where I need
a function to perform several duties, but the duties to perform (although
decided at the point of call, at compile time) are not controlled by the
signature (the arguments are the same) so overloading normally won't help
(unless abused like new-expression does with std::nothrow). There is also a
need to remove any redudant or duplicated code. I will take a "real world"
example, supose I want to "beautify" and extend/wrap the mkdir(2) POSIX
call. Requirements (acording to the code that will use it):
1. beautify:
1.1 it will take not only char const* for pathname but also std::string
const& (most likely to be used alot more oftenthan the char const* version)
1.2 also, mkdir(2) takes 2 arguments, a path and a second argument which
represents the privileges to be created with for that directory;
beautification asks us to make this second argument a default argument of
value 0755
2. extend/changed semantics:
2.1 there must be a nothrow version that returns with bool if it succeeded
2.2 there must be a throw version that throws in case of error
2.3 there must be a version where the code should NOT consider an error if
mkdir(2) returns -1 and errno == EEXIST and there is a directory already
2.4 there must be a version to be used when the caller does not care about
any possible failure (best effort version, this version is needed because
in order to properly solve requirement 2.3 one needs to use an expensive
(l)stat because EEXIST is also returned in case of non directories existing
at the same path and as such, some users may just want a best effort mkdir
without the cost of doing stat in case of EEXIST failure)
Sounds simple and straightforward
(the char const* taking versions are not showed for brevity although the
real code has the std::string const& taking versions be just inline
relayers to the char const* versions using .c_str() on the received path)
bool mkdir_nothrow(std::string const& path, mode_t privs = 0755) {
struct stat stbuf;
return !::mkdir(path.c_str(), privs) || (errno == EEXIST
&& !::lstat(path.c_str(), &stbuf) && S_ISDIR(stbuf.st_mode));
}
void mkdir(std::string const& path, mode_t privs = 0755) {
if (!mkdir_nothrow(path, privs))
throw std::runtime_error("mkdir");
}
The above code should satisfy all requirements except 2.4. To satisfy 2.4
one may argue the user may just directly call mkdir(2) which does just
that, but mkdir(2) does not satisfy the beautification requirements.
Because I want "syntax compression" (that is short, expressive syntax) for
all these mkdir cases I thought a better solution is needed, something
like:
enum {
mkdir_nothrow = 0,
mkdir_throw = 1,
mkdir_noexists = 0,
mkdir_exists = 2
}
template<unsigned Options>
typename if_<Options & mkdir_throw, void, bool>::type
mkdir(std::string const& path, mode_t privs = 0755) {
bool res = !::mkdir(path.c_str(), privs);
if (Options & mkdir_exists) {
struct stat stbuf;
res ||= errno == EEXIST && !::lstat(path.c_str(), &stbuf) &&
S_ISDIR(stbuf.st_mode);
}
if ((Options & mkdir_throw) && !res)
throw std::runtime_error("mkdir");
return typename if_<Options & mkdir_throw, void, bool>::type(res);
}
Well, maybe the implementation could be better (please provide better
examples, I'm pretty new into this kind of coding style), I realise I could
use some template specializations instead of relying on the compiler
optimizer with "if (Options & various)" being optimized out based on
compile time known values and also the last line/conversion. But I am more
interested to know if such way to solve things is fairly common, I have a
strong feeling I'm missing something big that I had to write all this stuff
just to have flexible behaviour control over a simple function like that.
The end result is pretty nice tho:
mkdir<mkdir_nothrow | mkdir_exists>(path);
or mkdir<mkdir_throw | mkdir_noexists>(path, 0700);
How do you people solve similar issues?