LUMIERA.clone/research/try.cpp
Ichthyostega 847593f18b Investigation: resolve the mystery and fix the problem
Oh well.
This kept me busy a whole day long -- and someone less stubborn like myself
would probably supect a "compiler bug" or put the blame on the language C++

So to stress this point: the compiler behaved CORRECT

Just SFINAE is dangerous stuff: the metafunction I concieved yesterday requires
a complete type, yet, under rather specific circumstances, when instantiating
mutually dependent templates (in our case lib::diff::Record<GenNode> is a
recursive type), the distinction between "complete" and "incomplete"
becomes blurry, and depends on the processing order. Which gave the
misleading impression as if there was a side-effect where the presence
of one definition changes the meaning of another one used in the same
program. What happened in fact was just that the evaluation order was
changed, causing the metafunction to fail silently, thus picking
another specialisation.
2017-12-02 02:51:51 +01:00

118 lines
4.8 KiB
C++

/* try.cpp - for trying out some language features....
* scons will create the binary bin/try
*
*/
// 8/07 - how to control NOBUG??
// execute with NOBUG_LOG='ttt:TRACE' bin/try
// 1/08 - working out a static initialisation problem for Visitor (Tag creation)
// 1/08 - check 64bit longs
// 4/08 - comparison operators on shared_ptr<Asset>
// 4/08 - conversions on the value_type used for boost::any
// 5/08 - how to guard a downcasting access, so it is compiled in only if the involved types are convertible
// 7/08 - combining partial specialisation and subclasses
// 10/8 - abusing the STL containers to hold noncopyable values
// 6/09 - investigating how to build a mixin template providing an operator bool()
// 12/9 - tracking down a strange "warning: type qualifiers ignored on function return type"
// 1/10 - can we determine at compile time the presence of a certain function (for duck-typing)?
// 4/10 - pretty printing STL containers with python enabled GDB?
// 1/11 - exploring numeric limits
// 1/11 - integer floor and wrap operation(s)
// 1/11 - how to fetch the path of the own executable -- at least under Linux?
// 10/11 - simple demo using a pointer and a struct
// 11/11 - using the boost random number generator(s)
// 12/11 - how to detect if string conversion is possible?
// 1/12 - is partial application of member functions possible?
// 5/14 - c++11 transition: detect empty function object
// 7/14 - c++11 transition: std hash function vs. boost hash
// 9/14 - variadic templates and perfect forwarding
// 11/14 - pointer to member functions and name mangling
// 8/15 - Segfault when loading into GDB (on Debian/Jessie 64bit
// 8/15 - generalising the Variant::Visitor
// 1/16 - generic to-string conversion for ostream
// 1/16 - build tuple from runtime-typed variant container
// 3/17 - generic function signature traits, including support for Lambdas
// 9/17 - manipulate variadic templates to treat varargs in several chunks
// 11/17 - metaprogramming to detect the presence of extension points
// 11/17 - detect generic lambda
// 12/17 - investigate SFINAE failure. Reason was indirect use while in template instantiation
/** @file try.cpp
** Bug hunt: a typedef detecting metafunction failed mysteriously.
** As it turned out, the implemented check required a _complete definition_, since it applied `sizeof()`.
** But the Detection was invoked indirectly from a template while this was still in instantiation.
** This explains why the metafunction worked in a clean test setup, but failed when integrated
** in the actual code base. The tricky and insidious part of this story is the fact, that
** in a regular definition, this would cause a compilation failure. But since our detector
** relies on SFINAE, just the detection went wrong, and consequently an improper template
** specialisation was picked. These are the perils of metaprogramming with a language
** never really made for functional programming...
**
** The solution or workaround is simple: use a detection technique able to work with incomplete types.
**
** For context: lib::diff::Record defines various Iterators. And lib::dif::GenNode is a _recursive datatype_,
** which means, already the definition of GenNode requires the instantiation of `Record<GenNode>`. Now we added
** some detector magic to our Iterator Adapters, and this magic failed on the Iterators defined by `Record,`
** because `Record<GenNode>` was not fully instantiated at that point.
*/
typedef unsigned int uint;
#include "lib/format-cout.hpp"
#include "lib/meta/util.hpp"
#include "lib/util.hpp"
#include <vector>
template<typename X, typename XX = typename X::value_type>
struct Test_Incomplete
{ };
template<typename X, int s = sizeof(typename X::value_type)>
struct Test_Complete
{ };
using lib::meta::Yes_t;
using lib::meta::No_t;
template<class X>
static Yes_t detectComplete(Test_Complete<X> * );
template<class>
static No_t detectComplete(...);
template<class X>
static Yes_t detectIncomplete(Test_Incomplete<X> * );
template<class>
static No_t detectIncomplete(...);
struct InStatuNascendi
{
using Iter = std::vector<InStatuNascendi>::iterator;
static const auto detectedComplete = (sizeof(Yes_t) == sizeof(detectComplete<Iter>(0))); // will produce No, erroneously
static const auto detectedIncomplete = (sizeof(Yes_t) == sizeof(detectIncomplete<Iter>(0))); // will produce Yes
};
#define SHOW_TYPE(_TY_) \
cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() <<endl;
#define SHOW_EXPR(_XX_) \
cout << "Probe " << STRINGIFY(_XX_) << " ? = " << _XX_ <<endl;
int
main (int, char**)
{
SHOW_EXPR (InStatuNascendi::detectedComplete);
SHOW_EXPR (InStatuNascendi::detectedIncomplete);
cout << "\n.gulp.\n";
return 0;
}