Closed SimonKagstrom closed 9 years ago
The one needed feature of C++14 is auto in lambda. It is probably possible to work around, but it's not trivial (I have tried, although not very hard, and failed.) This is for the parameter to the lambda in the *_(capture, ...) macros. Since the type is known in call_modifier<> template, which the lambda is given to, it should be possible to grab the type from there, but I've so far failed to do so.
OK, I understand. I guess the travis-case can be worked around by installing a newer compiler from some ppa before building the unit tests.
Ouch, so GCC 4.9 is required? Unfortunately even Fedora 20 won't cut it then.
Perhaps a clever macro solution can be used instead ;-)
Ugh, I'm sure some clever macro solution can be used. I'm trying to reduce the frequency of those, not increase it...
clang 3.5 works fine too.
I'm closing this now. Perhaps I, or someone else, comes up with a clever solution, in which case it'll be incorporated, but chances are c++14 will become mainstream before that.
I am willing to work on backporting Trompeloeil to C++11. A couple of hours work has resolved the trivial issues (type trait helpers, exchange, integer_sequence) and am ready to attempt the generic lambdas and decltype(auto)
. I don't really want to maintain a fork "forever". Is there enough interest to accept a contribution on this issue back to the project? (I will only be targeting GCC 4.8.3 in the first instance.)
decltype(auto)
is probably possible to work around, but I think you may find generic lambdas to be the killer. When I first started on this project, C++11 was on my mind, but I couldn't figure out a way around generic lambdas. That said, my inability to find a solution doesn't mean that none exists.
I have mixed feelings about this, though. On the one hand I find your enthusiasm commendable, and the chance for widened support a very good thing indeed. On the other hand, I'm have my attention in the other temporal direction, and try to see what of C++17 I can take advantage of (as possible extensions) without sacrificing C++14 support.
Yes, my enthusiasm for this issue will never be higher than now. I remain undeterred and will proceed.
I share your desire to look forward instead of backward. Perhaps the following strategy could allay you.
Let's say the best possible outcome actually occurs: I succeed with a C++11 backport, the code review is passed, and you merge the pull request into this project. In the release that you announce (limited) support for C++11 you also immediately deprecate its support. You remain free to drop C++11 support at the time of your choosing, in particular at the time you decide to add a useful C++17-based feature that impacts a C++11-sensitive code path.
My needs are short-term (effectively one release only). I hope anyone who needs C++11 support now is using it as a stepping stone release to C++14.
Of course, if I don't succeed, then I will add this to my list of heroic failures and the set of Those-Who-Tried-and-Failed will have increased by one. We form a club, have a whisky someday, maybe the next ACCU conference, and drink a toast to "hard problems without solutions (so far)." No hard feelings.
;-)
You may want to have a look at the "detector" branch and see if the detection idiom is working with g++ 4.8 -std=c++11. I intend to merge that into "develop" very soon, since it has no negative impact on compilation time, and it makes the source easier to read.
Progress Report
Good news, so far. I started from the detector
branch and have code that compiles clean with g++-4.8 -std=c++11
(GCC 4.8.5). No issues so far with void_t
and related detector code. All generic lambdas have been lowered to function objects. decltype(auto)
has its own backward compatibility macros. Yes, macros, but only two. On to testing, ChangeLog
and administration.
I'm not very familiar with the process of creating a branch and getting it reviewed. Please advise the steps to take to get this code into the repo in a way that meets the project guidelines.
This sounds exciting indeed. I'm very curious how you've managed to replace the generic lambdas with function objects in .RETURN()
, .THROW()
, .WITH()
and .SIDE_EFFECT()
.
For tests, get compiling_tests.cpp
through and you should be more or less set. It needs Catch! I believe the tests for regex will fail, so those probably need to be #ifdef
:ed on c++ version. Maybe some others too.
It's also highly desirable to get the compilation error tests through. CXX=g++-4.8.5 ./check_errors.sh
You probably need to alter the script since it explicitly sets -std=c++14
.
This weekend I killed the detector
branch after having merged it into develop
, To move on, please get a nice set of commits for the functionality on the develop
branch, and issue a pull request. I'd appreciate an updated .travis.yml
to be included, since otherwise I'd be sure to accidentally break g++-4.8 at any time.
I think I understand, thanks for the pointers. Will switch to develop
branch and take it from there.
Once code is proven working, I'll blog about the techniques used.
Update: OOPS - missed the content of the macros .RETURN()
, .THROW()
, .WITH()
and .SIDE_EFFECT()
! Of course this came to light with the first run of compiling_tests.cpp
. Well, that's where the real work is then.
Positive news, of a sort.
I have a version of Trompeloeil with alternative macros for
.RETURN()
, .THROW()
, .WITH()
, and .SIDE_EFFECT()
and
their LR_
siblings that compiles with g++-4.8 -std=c++11
.
These macros take two extra arguments, obj
and func
and
I have called them .WITH_MOCK_ARGS()
etc. for want of a
better name (do you have a better suffix?).
The extra arguments are used to extract the type of trompeloeil_x
,
which is always call_params_type_t<sig>
, as follows
#define TROMPELOEIL_WITH_MOCK_ARGS_(capture, obj, func, arg_s, ...) \
with( \
arg_s, \
[capture]( \
decltype( \
(obj).TROMPELOEIL_CONCAT(trompeloeil_tag_,func) \
)::call_params_type_t const& \
trompeloeil_x) \
{ \
:::
})
call_params_type_t
is a using
declaration in
struct TROMPELOEIL_LINE_ID(tag_type_trompeloeil)
defined as
using call_params_type_t = ::trompeloeil::call_params_type_t<sig>;
when expanding the macro TROMPELOEIL_MAKE_MOCK_()
.
I tried hard to keep the existing interface, but have to this point
been unable to extract the required type information from the
context in which the lambda is evaluated. Maintaining the elegance
of the fluent interface for defining expectations was a concern of
mine. I am most interested if you (or anyone!) finds a way to
communicate the type for trompeloeil_x
across the code generated
in the REQUIRE_CALL()
macro (and siblings) and .WITH()
etc.
These new macros also work in C++14 mode so there is a mechanical, incremental, migration path to the current API when g++ 4.8.x is finally laid to rest.
I am working through compiling_tests.cpp
, duplicating the
test cases and changing the copies to use the new macros.
All original test cases run in C++14 mode, so nothing has
been lost.
G++ 4.8.5 has incomplete support for <regex>
, so for
the moment test cases that fail because of this are conditionally
enabled only for G++ 4.9.0 or above. I will revisit them after
my first pass through the file and see what can be done.
Clang support is maintained. Microsoft compilers are as yet untested.
Onward!
Yes, you've reached exactly the same stumbling block as I did when I tried, although I gave up where you persist.
I'm trying to think of a way to get the types across. If I'm successful, I'll let you know, although in honesty I'm not optimistic.
I have an idea. I'll do some experiments if I find the time, but this week is pretty nutty, so no promises.
The outline, though, is that there is a type template:
template <unsigned long N>
struct param_type_access
{
using type = typename param_type_access<N-1>::type;
};
template <>
struct param_type_access<0>
{
};
REQUIRE_CALL()
(and siblings) make a specialization:
template <>
struct param_type_access<__LINE__>
{
using type = call_params_type_t;
};
And then .WITH()
etc. access the type via param_type_access<__LINE__>::type
,
which should find the specialization on the same line, or the next few lines above.
This will obviously break apart if you have several expectations on the same line, but the idea probably works, albeit with a measurable penalty in compilation time.
Genius. I added a partial implementation to try the idea out
0
in namespace trompeloeil
.Modified TROMPELOEIL_REQUIRE_CALL_
to this code
#define TROMPELOEIL_REQUIRE_CALL_(obj, func, obj_s, func_s) \
template <> \
struct param_type_access<__LINE__> \
{ \
using type = \
decltype( \
(obj).TROMPELOEIL_CONCAT(trompeloeil_tag_,func) \
)::call_params_type_t; \
}; \
auto TROMPELOEIL_COUNT_ID(call_obj) = TROMPELOEIL_REQUIRE_CALL_OBJ(obj, func,\
obj_s, func_s)
WITH_MOCK_ARGS
to
#define TROMPELOEIL_WITH_MOCK_ARGS_(capture, obj, func, arg_s, ...) \
with( \
arg_s, \
[capture]( \
typename param_type_access<__LINE__>::type const& \
trompeloeil_x) \
{ \
Compiled compiling_tests.cpp
with this change and immediately hit this error
trompeloeil.hpp:3912:3: error: a template declaration cannot appear at block scope
template <> \
^
trompeloeil.hpp:3909:3: note: in expansion of macro ‘TROMPELOEIL_REQUIRE_CALL_’
Unfortunately it appears we cannot remedy the situation using this technique directly.
I gave some thought to having a macro to declare the necessary machinery at namespace scope, e.g.
#define TROMPELOEIL_PARAM_TYPE_ACCESS(obj, func) \
template <> \
struct param_type_access<__LINE__> \
{ \
using type = \
decltype( \
(obj).TROMPELOEIL_CONCAT(trompeloeil_tag_,func) \
)::call_params_type_t; \
};
but all uses of obj, func
have to be made before introducing another param_type_access
specialization using the macro, else the modifiers will receive the wrong type when they
perform their lookups. For example, this fails
TROMPELOEIL_PARAM_TYPE_ACCESS(obj, getter(ANY(unmovable&)))
TROMPELOEIL_PARAM_TYPE_ACCESS(obj, getter(_, _))
void foo()
{
REQUIRE_CALL(obj, getter(ANY(unmovable&)))
.RETURN(_1); // wrong call_param_type_t returned
REQUIRE_CALL(obj, getter(_, _))
.SIDE_EFFECT(_2 = std::to_string(_1));
}
Yes, i thought about that during lunch and was just about to retract the suggestion.
I think something along those lines may be a way forward though. Perhaps defining a variable that you can look up and get a type from? The trick is, of course, to find the name without causing collisions when you have several expectations in the same scope.
More progress, I suppose. Since a reliable method of communicating type information between macros has not been found to date, I decided to make the mock type information explicit and move on.
I changed the surface syntax macros for C++11 to the following short names,
(along with their LR_
variants):
NAMED_MOCK_TYPE(obj, func) *new*
WITH_TYPE(mtype, ...)
SIDE_EFFECT_TYPE(mtype, ...)
RETURN_TYPE(mtype, ...)
THROW_TYPE(mtype, ...)
where the new macro NAMED_MOCK_TYPE()
is just
#define TROMPELOEIL_NAMED_MOCK_TYPE(obj, func) \
decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_tag_,func))
To use it, write it in a type alias declaration,
using m_t = NAMED_MOCK_TYPE(obj, getter(ANY(int)));
and pass the type alias name as the first argument to the new macros
REQUIRE_CALL(obj, getter(ANY(int)))
.RETURN_TYPE(m_t, 1)
.IN_SEQUENCE(seq);
By leaving it to the developer to provide the type alias name, multiple type names are supported in the same scope, one for each different expectation.
When migrating to C++14 and beyond, the transformation of removing the
alias declaration and replacing '_TYPE(<mtype>,'
with '('
for the matching
macros is all that's required.
All unit tests in compiling_tests.cpp
have been modified to work with both
C++11 and C++14, though some are conditionally enabled, depending on
compiler mode. All C++11 tests are run in C++11 mode (-std=c++11
) and
C++14 mode (-std=c++14
), while C++14 tests are run in C++14 mode or later.
Each unit test is now tagged as [C++11][C++14]
or [C++14]
in addition
to their previous tags.
I have performed a few runs with this script and am examining the regressions compared to the baseline the original source provides.
Not quite ready to bring this online.
<regex>
The problems with executing regular expressions is mostly due to libstdc++
.
I have had much more success with libc++
(Ubuntu package libc++1
3.9.1-2)
but currently have been unable to link this library with code compiled with
g++-4.8 -std=c++11
. Command lines that work with every other version of
g++
fail with this version with undefined references
.
g++-4.8 -std=c++11
gives "conversion is ambiguous" in duck_typed_matcher
and wildcard
if both opertor V&&() const
and operator V&() const
are defined
at the same time. I have had to disable V&&()
and provide explicit
conversions for necessary types used in the unit tests. For example,
operator std::string&&() const
template <typename T>
operator std::unique_ptr<T>&&() const;
operator int&&() const;
This is a serious compiler bug with respect to Trompeloeil and really limits
its general utility when used with g++-4.8
. However, specific measures
can be put in place to workaround this deficiency for a fixed codebase.
None of the neg_matcher
test cases are enabled at present, due to conversion
problems with duck_typed_matcher
. Using a disambiguated function template
e.g. !trompeloeil::eq<std::string>("foo")
is a workaround for this defect.
Most (90%?) of the functionality of Trompeloeil works in C++11 mode. This is good enough for my continued effort to complete the outstanding work prior to a commit for review.
In addition to the work implied by the above report, I want to reduce the
turnaround time on compiling compiling_tests.cpp
. The file is now over
10500 lines and stressing out CLion (sorry JetBrains).
If this project makes it to production then I anticipate compiling the C++11
test cases a very few times while iterating heavily on C++14 and beyond code.
This suggests using the method suggested in the Catch! documentation to
separate the main
from the tests and further having a file for each of
the compilation modes.
I have the opportunity to compile the unit tests with Visual Studio 2017, so will try that before contemplating Travis.
The journey continues.
This looks promising. Even without your additions, CLion is having a really hard time. It's my preferred dev environment, but not when working with Trompeloeil. I hope it'll catch up soon.
I do wonder, though, how do you feel about the usability with the new _TYPE
macros? I feel that much of the simplistic elegance of use is lost with that quite repetitive code. Is that a price you think is worth paying when doing development in C++11?
I have only had a few minutes to consider the "price" of the _TYPE
macros. At the present time they are the difference between using Trompeloeil with a C++11 toolchain and having to use a different mock object framework. Therefore I am prepared to pay such a price.
As far as usability is concerned, yes, the goal was to have the elegance along with utility, but "C++14 changes everything" as Louis Dionne said. I regard this work as a step towards this goal and will continue to strive for its attainment.
In the meantime, there are four of the compilation_errors
test cases that fail with C++11 mode. The cause is the way I specify the return type of the lambda in the RETURN_TYPE
macro. What should be
decltype(auto)
is approximated with mtype::return_of_t
which is too eager.
I am committing this work to branch c++11
(hope I can create branches) which is not ready for review but is ready for use.
OK, can't commit to a branch directly. Advice?
I created a c++11
branch from head on develop
. I guess that if you get your fork in sync with my tree, you should be able to work from there.
Fork is up-to-date (actually, with v27 of master) and have pushed a c++11
branch there. Will continue resolving regressions and getting the branch ready for review.
I don't know if you saw my bugfix on branch develop
just now, but it may have an impact on your work.
Thanks for the heads up on the changes. Merged and tested the multiple inheritance fix (Issue #46) on g++-4.8.5 with success. Removed tabs.
I have started piloting the c++11
branch from 5 July. The saddest part was finding g++ 4.8.3 has a defect in <tuple>
which prevents Trompeloeil compiling. Fixed in 4.8.4.
See: bugzilla, "Bug 61947 - [4.8/4.9 Regression] Ambiguous calls when constructing std::tuple"
Available: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61947
A couple of test cases are also disabled with parsing problems, also fixed in either 4.8.4 or 4.8.5.
Also found that libc++ 3.9.1-2
on clang++-3.5
produces an ICE when compiling compiling_tests.cpp
. This will not be addressed.
Work continues to broaden the compiler platforms and get this branch ready for Travis.
Another heads-up for you.
On branch develop
I've just made major additions to CMakeLists.txt and .travis.yml (which now uses cmake.)
A part in that change is also a change in the directory structure to a more traditional one.
I plan on merging this to master
for release once I've gotten conan
to work again (I did not keep up when conan
moved to bintray
.)
Tested the c++11
branch on Visual Studio 2017 Community Edition
and resolved an error in my backport of re()
. Many
more unit tests are enabled as a result.
Introduced a macro, TROMPELOEIL_CPLUSPLUS
, to cope with Visual C++
setting _cplusplus == 199711L
.
Removed warnings from compiling_tests.cpp
by compiling with
the same command line flags used for Travis.
Tested with GCC 7.1.1 (Fedora 26) - pass.
decltype(auto)
Tried to improve the number of passes for check_errors.sh
.
At present four tests fail in C++11 mode:
return_value_to_ref.cpp
return_wrong_pointer_constness.cpp
return_wrong_reference_constness.cpp
return_wrong_type_first.cpp
where fail
means the desired static_assert
is not triggered.
After some research, thought and experimentation I conclude that
it is not possible to replace decltype(auto)
in the trailing return type
of the generic lambda in the macro RETURN()
with nothing (implying
auto
deduction of the return type), and certainly not with
decltype(__VA_ARGS__)
. Reworking the placeholder system is out-of-scope
of this issue.
The closest I have come is as it stands now,
-> mtype::return_of_t
, which forces the check too early in the compilation.
Therefore I don't expect any further improvement in the number of passes
of check_errors.sh
for C++11 mode in the near term.
All tests continue to pass in C++14 mode.
Merge latest from develop
.
Modify .travis.yml
to broaden C++ modes used. I am considering adding
stdc++
library support to the matrix of tests as well.
I have a couple of scripts, run_all_compiling_tests.sh
and
run_all_check_errors.sh
, that "mimics" the work of Travis,
but locally. Good for regression testing before a push, and
perhaps useful to others. Should these scripts be committed?
Not considered at present is measuring the performance of
compilation using the Trompeloeil header. How do you measure this?
I would like this branch to have some relation to the performance
of master
but don't have a baseline measure for comparison.
Hold on with the .travis.yml
changes for a day or two. I have received a pull request for some much desired changes to it (https://github.com/rollbear/trompeloeil/pull/47) but I have some questions I want answered before accepting it.
I don't think you need to add your scripts. The reworked CMakeLists.txt
(which works nicely from CLion, by the way,) is what is used from the updated .travis.yml
too. It doesn't have check_errors.sh
as a target, but that could be added.
I must admit that my compilation time measurements have all been rather ad-hoc. Whenever I have an idea for a change that may improve compilation time, I make a base line measurement from compiling_tests.cpp
with various compilers, make the change, and rerun the measurement. It would be a good thing to have a system that consistently measures the build times and stores the results so they can be visualised, but maybe that would be over zealous.
The .travis.yml
file stuff is done now. Some ugly work-arounds, but it works. The CMake stuff is looking quite good too. I don't expect any major changes to these any time soon, so no need to hold back any longer.
I forgot to mention that the provided CMakeLists.txt doesn't work with the CMake that is bundled with CLion. See https://youtrack.jetbrains.com/issue/CPP-8477
Using an external CMake works fine.
It seems the branch c++11
has survived "trial by Travis" and now
has a g++-4.8 -std=c++11
job that passes. See for example, \
https://travis-ci.org/AndrewPaxie/trompeloeil/builds/261471592
test/compiling_tests.cpp
I have also split test/compiling_tests.cpp
into these files
$ git add compiling_tests.cpp
$ git add compiling_tests.hpp
$ git add compiling_tests_11.cpp
$ git add compiling_tests_14.cpp
and updated CMakeLists.txt
accordingly. It may help build times in
Travis to add -j 3
to the make VERBOSE=1 self_test
command.
Locally, I have tracked down and installed clang-3.6
and an experimental
version of gcc-7
so now have all supported compilers on Ubuntu and
Windows at my fingertips.
I have had time to consider the modifications made to the C++14 macros
in order to get some progress with the C++11 backport. Recall that
as it stands the WITH
, SIDE_EFFECT
, RETURN
, and THROW
macros
were modified to take an extra argument, the "mock type" (more correctly,
the expectation type).
For example,
// C++ 14 expectation
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN(1);
is transformed to
// C++11 expectation, version one
using m_t = NAMED_MOCK_TYPE(obj1, count());
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN_TYPE(m_t, 1);
This was dissatisfying from the point of view of having to retype the expectation type as an argument in each macro that needs it. More serious is that all existing Trompeloeil documentation has to be reinterpreted when used in a C++11 context.
I still haven't found a solution to being able to convey the expectation
type from the e.g. REQUIRE_CALL
to the e.g. RETURN
macro as an
implicit argument. I believe the following approach however is a
better compromise compared to C++11 API version one.
Instead of an implicit argument, use an explicit argument, but this time expressed as a macro:
// C++11 expectation, version two
# define TROMPELOEIL_EXPECTATION_ARGS obj1, count()
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN(1);
# undef TROMPELOEIL_EXPECTATION_ARGS
The advantages of this approach, which I am calling C++11 API version two, are:
TROMPELOEIL_EXPECTATION_ARGS
macro simply
becomes unnecessary and may be removed in a separate pass over the code.The principal disadvantage is, of course, having to type a #define
and
#undef
pair for each expectation, even though at first glance the
information needed is already presented in the arguments to the expectation.
There may be a way to tune this even further, perhaps with additional macros to capture the essence of what's required:
TROMPELOEIL_START_EXPECTATION(obj, count())
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN(1);
TROMPELOEIL_END_EXPECTATION
Onward to the C++11 API, version two, and documentation drawn from the approximately 13,000 lines of working notes I have maintained during the prosecution of this issue.
This gives me an idea.
Instead of a START/END pair, you could probably make a wrapper macros, something like:
#define REQUIRE_CALL11(obj, func, ...) \
using decltype(TROMPELOEIL_LINE_ID(ret_type)) = decltype((obj).func); \
TROMPELOIEIL_REQUIRE_CALL(obj, func), __VA_ARGS__)
You'd then have C++11 versions of RETURN()
etc, that makes use of TROMPELOEIL_LINE_ID(ret_type)
.
Usage would look like:
REQUIRE_CALL11(obj, func(),
RETURN11(3)
);
This should work because the expansion of the REQUIRE_CALL11
macro will include the RETURN11()
macro, with the same __LINE__
, so they see the same ret_type
.
The naming is not a suggestion, btw, I think it's appalling, I just wanted to highlight what would need to change.
I had a similar idea after seeing my first START/END macro attempts fail miserably. I will follow this line and see where it leads.
I have completed a cut of the C++11 version two API that is a balance
between the macro form you suggested and the START
/END
macros where
the design started.
I created a single MAKE_EXPECTATION
macro to wrap around an expectation,
#define TROMPELOEIL_MAKE_EXPECTATION(obj, func, ...) \
using TROMPELOEIL_LINE_ID(e_t) = \
decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_tag_,func)); \
__VA_ARGS__
Example,
MAKE_EXPECTATION(obj1, count(),
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN(1)
);
The WITH
, RETURN
, SIDE_EFFECT
, and THROW
macros reference
TROMPELOEIL_LINE_ID(e_t)
from which call_params_type_t
and
return_of_t
may be accessed.
The reason I didn't place the using
declaration within the
REQUIRE_CALL
is because there is not always a call modifier to
the call. This means the type introduced by the using
may,
in fact, go unused, leading to the warning -Wunused-local-typedefs
from GCC when -Wall
is used.
This retains a smooth migration path to C++14 API, but has
the downside of requiring multiple mentions of the obj
, func
pair.
This is a statement-oriented implementation. One cannot place a
TROMPELOEIL_MAKE_EXPECTATION
macro around an expectation used as a
function parameter, default argument, member of an initializer list,
member initialization in a constructor, or any other place where an
expression is required.
For code such as,
e[0] = NAMED_REQUIRE_CALL(obj, getter(ANY(int)))
.IN_SEQUENCE(s)
.RETURN(0);
the C++11 version two API is,
MAKE_EXPECTATION(obj, getter(ANY(int)),
e[0] = NAMED_REQUIRE_CALL(obj, getter(ANY(int)))
.IN_SEQUENCE(s)
.RETURN(0)
);
This works because no additional scope is introduced by the
MAKE_EXPECTATION
macro. It cannot be written as
e[0] = MAKE_EXPECTATION(obj, getter(ANY(int)),
NAMED_REQUIRE_CALL(obj, getter(ANY(int)))
.IN_SEQUENCE(s)
.RETURN(0)
);
given the current implementation of the macro since the macro introduces
a using
statement into the token stream. Still, the current implementation
can accomplish a lot of work.
This should work because the expansion of the
REQUIRE_CALL11
macro will include theRETURN11()
macro, with the same__LINE__
, so they see the sameret_type
.
This assumption turned out to be only partially correct. It holds for GCC and Visual Studio, but not for Clang.
This transcript shows that, for Clang, the value for __LINE__
varies at
different points of the macro expansion.
$ g++ --version
g++ (Ubuntu 7-20170407-0ubuntu2) 7.0.1 20170407 (experimental) [trunk revision 246759]
Copyright (C) 2017 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.
$ g++ -E -x c++ - <<!
#define MAKE_EXPECTATION(...) \
Is MAKE_EXPECTATIONs __LINE__ \
__VA_ARGS__
#define RETURN(...) \
= __LINE__ RETURNs \
__VA_ARGS__
MAKE_EXPECTATION(
RETURN(?)
)
!
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "<stdin>"
Is MAKE_EXPECTATIONs
7
# 5 "<stdin>"
= 7 RETURNs ?
# This is true for all versions of GCC we care about.
$ clang++-4.0 --version
clang version 4.0.0-1ubuntu1 (tags/RELEASE_400/rc1)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ clang++-4.0 -E -x c++ - <<!
#define MAKE_EXPECTATION(...) \
Is MAKE_EXPECTATIONs __LINE__ \
__VA_ARGS__
#define RETURN(...) \
= __LINE__ RETURNs \
__VA_ARGS__
MAKE_EXPECTATION(
RETURN(?)
)
!
# 1 "<stdin>"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "<stdin>" 2
Is MAKE_EXPECTATIONs 7 = 6 RETURNs ?
# This is true for all versions of Clang we care about.
As a result, I have not been able to remove the C++11 version one API.
There remains an issue with an AddressSanitizer error with
g++-7 -std=c++14 stdc++
. See this report on Travis for details,
https://travis-ci.org/AndrewPaxie/trompeloeil/jobs/264143994
The cause could be any of a number of issues:
libstdc++v3
implementation of <regex>
truly has a stack use defect.g++-7
is not an official release. Maybe retrying with
g++-7.2.x
in the next few days will reveal it is resolved.I'm having trouble putting this repetition of the signature to rest (feel free to ignore my ramblings if they disturb your progress.)
It occurred to me that a way around the __LINE__
hack may be to enclose it all in a lambda.
A macro like:
REQUIRE_CALL11(obj, func,...) \
auto TROMPELOEIL_COUNT_ID(trompeloeil_expectation) = \
[&](){ \
using trompeloeil_calll_type = NAMED_MOCK_TYPE(obj1, count()); \
return NAMED_REQUIRE_CALL(obj, func)
... /* ugly macro hackery here using trompeloeil_call_type */
}();
The idea being that the type is now local to the body of the lambda. Being in a local body, there's no need to use macro hackery to ensure a unique name for each invocation.
I like this alternative and will see if it can be expanded into a a version three API. I have also been troubled by this repetition.
The g++-7 ASan error remains elusive. I am learning a great deal
about ASan and the GCC implementation of regexes as I debug a
small example with rr
.
The issue with crashes running ASan with g++-7 was due to a defect in the compiler. This had been detected and reported by @MattGodbolt in June 2017. See
bugzilla.gcc.org, "Bug 81021 - stack-use-after-scope false positive error with exceptions," 8 June 2017. \ Available: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81021 \ Accessed: 23 August 2017
This has been marked as RESOLVED FIXED and an inspection of the source code for g++-7.2.0 released for Ubuntu Artful Aardvark shows that at least for this version of g++-7.2 the fix has been applied.
This doesn't mean that using ASan is going to be trouble-free. Consider, for example, this issue
"#837 - Recent kernel causes -fPIE ASan executables to abort on x86_64," \ Available: https://github.com/google/sanitizers/issues/837 \ Accessed: 23 August 2017
This means that currently on kernels such as that run by Artful Aardvark, a run with ASan enabled initializes about one time in ten and errors out the remainder of the times.
I have version three of the C++11 API implemented and committed, built upon your idea to use a lambda.
I managed to boil the implementation down to two macros, one for the case
of zero variadic arguments and the other for the case of one or more
variadic arguments. These are called, REQUIRE_CALL_0
and REQUIRE_CALL_V
(for variadic, after similar macros in Boost.Preprocessor).
Here's the implementation of TROMPELOEIL_REQUIRE_CALL_V
,
#define TROMPELOEIL_REQUIRE_CALL_V(obj, func, ...) \
TROMPELOEIL_REQUIRE_CALL_V_(obj, func, #obj, #func, __VA_ARGS__)
#define TROMPELOEIL_REQUIRE_CALL_V_(obj, func, obj_s, func_s, ...) \
auto TROMPELOEIL_COUNT_ID(call_obj) = \
TROMPELOEIL_REQUIRE_CALL_V_LAMBDA(obj, func, obj_s, func_s, __VA_ARGS__)
#define TROMPELOEIL_REQUIRE_CALL_V_LAMBDA(obj, func, obj_s, func_s, ...) \
[&] \
{ \
using s_t = decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_self_, func)); \
using e_t = decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_tag_,func)); \
\
return TROMPELOEIL_REQUIRE_CALL_LAMBDA_OBJ(obj, func, obj_s, func_s) \
__VA_ARGS__ \
; \
}()
#define TROMPELOEIL_REQUIRE_CALL_LAMBDA_OBJ(obj, func, obj_s, func_s) \
::trompeloeil::call_validator_t<s_t>{(obj)} + \
::trompeloeil::detail::conditional_t<false, \
decltype((obj).func), \
e_t> \
{__FILE__, static_cast<unsigned long>(__LINE__), obj_s "." func_s}.func
// C++11 expectation, version one
using m_t = NAMED_MOCK_TYPE(obj1, count());
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN_TYPE(m_t, 1);
// C++11 expectation, version two
MAKE_EXPECTATION(obj1, count(),
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN_TYPE(m_t, 1));
// C++11 expectation, version three
REQUIRE_CALL_V(obj1, count(),
.IN_SEQUENCE(seq)
.RETURN(1));
This is much more expressive and even closer to the C++14 API,
// C++14 expectation
REQUIRE_CALL(obj1, count())
.IN_SEQUENCE(seq)
.RETURN(1);
It also compiles and runs on all supported compiler versions.
I note in passing the _0
form for use when there are zero call modifiers,
REQUIRE_CALL_0(obj2, func());
I have had to add -Wno-unneeded-member-function
to the list of suppressed
warnings in Clang, as it sees the functions trompeloeil_self_ ## name
and
trompeloeil_tag_ ## name
as unneeded e.g. when the class declaration
is embedded in an unnamed namespace. It would be good to add the
[[maybe_unused]]
attribute to their declarations when C++17 becomes
a supported compiler mode.
I am looking for a way to collapse the REQUIRE_CALL_0
and REQUIRE_CALL_V
macros into one REQUIRE_CALL_V
macro. I took a look at how the function
specifiers (override
) were handled in the MAKE_MOCKn
macros. I haven't
been able to get rid of the trailing commas in the __VA_ARGS__
. This
wasn't an issue in MAKE_MOCKn
as the __VA_ARGS__
was never referenced
in the body of TROMPELOEIL_MAKE_MOCK_
.
It is a shame that Standard C++ (and -Wpedantic
) insists on there being
at least one argument in __VA_ARGS__
.
I will continue my search for a feasible solution while completing the next steps in getting this branch ready for a pull request. It is likely that if a version four C++11 API is indeed possible, I will deprecate and remove all prior versions of the C++11 API. This will considerably simplify what has to be reviewed and merged.
As always, any insights you have would be greatly appreciated.
Really cool progress, I think.
__VA_ARGS__
is a pain for just the reason you say, and has been the source of many curses. One commonly used trick is to always add an extra parameter, ensuring that there's always one. But that just pushes the problem one step away and may not offer a solution anyway. In this case I think it would work, though. __VA_ARGS__
would then include the function, and you pass it on to another macro with REQUIRE_CALL_IMPL(obj, __VA_ARGS__, harmless_empty_modifier)
, where REQUIRE_CALL_IMPL
is your current REQUIRE_CALL_V
. It's just a matter of creating the harmless_empty_modifier
(something that just returns *this
, and give it a saner name.)
[[maybe_unused]]
is of course easily handled:
#if __cplusplus >= 201703L
# define TROMPELOEIL_MAYBE_UNUSED [[maybe_unused]]
#else
# define TROMPELOEIL_MAYBE_UNUSED
#endif
I'm a bit confused about that, though. Do you mean you get this warning when compiling with the C++14 API? If not, I think it's better to ensure that the troubling members are never generated when compiling with the C++11 API.
I had started in the direction you also suggest regarding __VA_ARGS__
. I have called the empty modifier function null_modifier()
. I will see if the "one more level of indirection" with the macro forwarding works in this case.
The -Wunneeded-member-function
warning is generated with all versions of Clang, in both C++11 mode and C++14 mode. The context is
namespace
{
class self_ref_mock
{
public:
void expect_self()
{
exp = NAMED_REQUIRE_CALL_0(*this, mfunc());
}
MAKE_MOCK0(mfunc, void());
std::unique_ptr<trompeloeil::expectation> exp;
};
} /* unnamed namespace */
The warning occurs during the compilation of what MAKE_MOCK0(mfunc,void())
expands.
Hope this helps.
As you suggest, I will continue to look for ways to avoid the warning rather than suppressing it.
The C++11 version three API consisted of two macros for each expectation,
(obj, func)
,
named with a _0
suffix.(obj, func, modifiers...)
, named with a _V
suffix.Now with the help these two very enjoyable references,
Jens Gustedt, "Default arguments for C99," 3 June 2010. \ Available: https://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/ \ Accessed: 6 October 2017
Jens Gustedt, "Detect empty macro arguments," 8 June 2010. \ Available: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ \ Accessed: 6 October 2017
your suggestions, and six weeks labour, I have indeed been able to collapse
these two macros down to one, still named with the _V
suffix. Indeed,
the effect is to make the _0
macros optional.
It would have been easier with __VA_OPT__()
,
Thomas Köppe, "Comma omission and comma deletion," ISO/IEC JTC1 SC22 WG21 P0306R4, 12 July 2017. \ Available: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0306r4.html \ Accessed: 1 October 2017
but that's why the language evolves.
Migration forward to C++14 is maintained as the macros remain disjoint and both sets are defined when in C++14 mode.
This is the code for TROMPELOEIL_REQUIRE_CALL_V()
,
#define TROMPELOEIL_ADD_NULL_MODIFIER(...) \
TROMPELOEIL_ADD_NULL_MODIFIER_IMPL(__VA_ARGS__ .null_modifier())
#define TROMPELOEIL_ADD_NULL_MODIFIER_IMPL(...) __VA_ARGS__
#define TROMPELOEIL_REQUIRE_CALL_V(obj, func, ...) \
TROMPELOEIL_REQUIRE_CALL_V_(obj, \
func, \
# obj, \
# func, \
TROMPELOEIL_ADD_NULL_MODIFIER(__VA_ARGS__))
This is the code for TROMPELOEIL_REQUIRE_CALL_0()
,
// Optional. For backward compatibility with earlier version three API.
#define TROMPELOEIL_REQUIRE_CALL_0(obj, func) \
TROMPELOEIL_REQUIRE_CALL_V_(obj, func, # obj, # func, .null_modifier())
The remaining macros in the call chain,
TROMPELOEIL_REQUIRE_CALL_V_(obj, func, obj_s, func_s, ...)
TROMPELOEIL_REQUIRE_CALL_V_LAMBDA(obj, func, obj_s, func_s, ...)
TROMPELOEIL_REQUIRE_CALL_LAMBDA_OBJ(obj, func, obj_s, func_s)
are the same as in the previous version three API.
_0
macros.develop
up-to-date with
some small improvements discovered along the way, but otherwise
unrelated to the the C++11 backport.ChangeLog
.You will see code that might better come from a third-party library like Abseil. I have tried to keep close to the intent of minimizing dependencies, and licensing issues are also avoided.
Slightly off topic, but I'm presenting Trompeloeil again at the NDC{Techtown} conference on Friday next week, and I want to acknowledge your fabulous work.
What should I say about the state of C++11 support? I get the feeling that you're more or less finished, or am I reading too much into your update?
That's good news. Say:
It's coming and we're working hard to have a high quality release by the end of October 2017.
I am cleaning up the obsolete API now. All known instances of the _0
macros were removed from my production code base yesterday. My hope is to have at least the code ready by end of this week and work on the documentation in weekend. Minor pull requests are in a known TODO list and will come prior to the c++11
branch pull request.
The limitations outlined in earlier progress reports remain:
<regex>
, negating matchers, wildcard, and duck type matchers.RETURN
and LR_RETURN
modifiers limits compile-time detection of type mismatches, but an error message is still generated.These will be explained further in the documentation.
I will explain the journey (the what and the how) to the current best C++11 API in a series of blog posts once I have the current work list completed.
BTW I am attending the Pacific++ conference the week following your presentation, another motivation to achieve what I have set out to do.
A quick note to let you know I was mauled by the preprocessor when trying to remove _0
macros. Somehow I managed to develop the proposed code in an environment where the -Wpedantic
warning was not enabled. The implementation just isn't standard C++11/14.
Nevertheless, I have a backup set of macros, more complex, but working for g++
and clang++
.
Unfortunately the VC++ compiler has other ideas as to how to expand __VA_ARGS__
, which I suspect you have encountered before (witness TROMPELOEIL_IDENTITY
).
This will delay a few minor pull requests, but I still have wind in my sails and am tacking my way toward the finish line.
Yeah, the preprocessor is the gift that keeps giving. MSVCs long standing bugs makes it even worse.
No worries. It's not like you have committed to a due date (and I won't give any at my talk on Friday, just say that "it's nearly done, but not quite yet.")
I have completed a working implementation for MSVC and am running the regression tests on Travis now, https://travis-ci.org/AndrewPaxie/trompeloeil/builds/289857656 OSX builds running slow again as I write this, but I hope it will complete without errors.
Will refactor into a simpler form once other tasks are out of the way.
I have more or less completed the remaining tasks before creating a pull request for your consideration.
_0
macros have been removed._V
macros have been refactored into a simpler form.libc++
and sanitizer libraries.Outstanding
ChangeLog
.During this review period, I rewrote all the support for C++14 standard library features missing in the C++11 standard library. These entities are now implemented solely using the draft proposals made to the C++ standards committee. This changes the attribution to these proposals instead of e.g. cppreference.com and puts the provenance of the code on firmer ground.
I have had one more attempt to fix the return type mismatch
detection in the C++11 implementation of TROMPELOEIL_RETURN_()
.
Progress hasn't been enough to delay the C++11 pull request
any longer. Any further work will have to be handled as a
separate issue.
I look forward to crossing the finish line in the near future.
Pull request #67 has been created for your first pass review and feedback.
Would it be much work to reduce the compiler requirements to C++11 instead? E.g., the Ubuntu 12.04 compilers does not (as far as I know) support C++14, which makes travis.org builds difficult.