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.
This commit is contained in:
parent
2c53dc2e57
commit
847593f18b
5 changed files with 158 additions and 19 deletions
|
|
@ -10,7 +10,7 @@
|
|||
// 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
|
||||
// 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"
|
||||
|
|
@ -35,29 +35,68 @@
|
|||
// 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
|
||||
// 12/17 - investigate SFINAE failure. Reason was indirect use while in template instantiation
|
||||
|
||||
|
||||
/** @file try.cpp
|
||||
** Bug hunting: a typedef detecting metafunction fails under circumstances yet to be investigated.
|
||||
** 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/meta/value-type-binding.hpp"
|
||||
#include "lib/diff/gen-node.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
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::enable_if;
|
||||
|
||||
using ElmIter = lib::diff::RecordSetup<lib::diff::GenNode>::ElmIter;
|
||||
using RecIter = lib::diff::Rec::iterator;
|
||||
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_) \
|
||||
|
|
@ -70,10 +109,8 @@ using RecIter = lib::diff::Rec::iterator;
|
|||
int
|
||||
main (int, char**)
|
||||
{
|
||||
SHOW_TYPE (ElmIter);
|
||||
SHOW_TYPE (RecIter);
|
||||
|
||||
SHOW_TYPE (lib::meta::TypeBinding<ElmIter>::pointer);
|
||||
SHOW_EXPR (InStatuNascendi::detectedComplete);
|
||||
SHOW_EXPR (InStatuNascendi::detectedIncomplete);
|
||||
|
||||
cout << "\n.gulp.\n";
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/iter-adapter-ptr-deref.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
|
|||
|
|
@ -70,11 +70,19 @@
|
|||
** Effectively this means that an error in the test expression might go unnoticed;
|
||||
** you'd be better off explicitly checking the detection result by an unit test.
|
||||
**
|
||||
** There are several typical problems to care about
|
||||
** There are several *typical problems* to care about
|
||||
** - a member can be both a variable or a function of that name
|
||||
** - function signatures need to match precisely, including const modifiers
|
||||
** - the generated metafunction (template) uses a type parameter 'TY', which could
|
||||
** shadow or conflict with an type parameter in the enclosing scope
|
||||
** - some of the detectors _require a complete type_ to work properly. They create a
|
||||
** pointer-to-member or invoke `sizeof()`. In regular code, doing such on an incomplete
|
||||
** type would provoke a _compilation failure_ -- however, here this code gets evaluated
|
||||
** in a SFINAE context, which means, it will fail silently and thus produce a wrong
|
||||
** detection result. This can be quite *insidious* when relying on the proper detection
|
||||
** to pick the right implementation/specialisation; especially when instantiating
|
||||
** _mutually dependent_ templates, the distinction between "complete" and "incomplete"
|
||||
** can be rather arbitrary while in the process of instantiation.
|
||||
** - the member and function checks rely on member pointers, which generally refer to
|
||||
** the explicit static type. These checks won't see any inherited members / functions.
|
||||
** - obviously, all those checks are never able to detect anything depending on runtime
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ namespace meta {
|
|||
template<typename TY>
|
||||
class has_nested_ValueTypeBindings
|
||||
{
|
||||
template<typename X, int i = sizeof(typename X::value_type)
|
||||
, int j = sizeof(typename X::reference)
|
||||
, int k = sizeof(typename X::pointer)
|
||||
template<typename X, typename XX = typename X::value_type
|
||||
, typename XY = typename X::reference
|
||||
, typename XZ = typename X::pointer
|
||||
>
|
||||
struct Probe
|
||||
{ };
|
||||
|
|
|
|||
|
|
@ -21692,6 +21692,99 @@
|
|||
</html></richcontent>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1512178947582" ID="ID_1778761323" MODIFIED="1512178958161">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Lessons
|
||||
</p>
|
||||
<p>
|
||||
learned
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<node CREATED="1512178960281" FOLDED="true" ID="ID_1847018554" MODIFIED="1512179228013" TEXT="SFINAE">
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1512178984189" ID="ID_226963358" MODIFIED="1512179008554" TEXT="Fehler im Check scheitern stillschweigend">
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
</node>
|
||||
<node CREATED="1512179012761" ID="ID_1425607905" MODIFIED="1512179029688">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
auf <i>incomplete type</i> achten
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<node CREATED="1512179030982" ID="ID_895612426" MODIFIED="1512179034418" TEXT="sizeof()"/>
|
||||
<node CREATED="1512179035013" ID="ID_1663591306" MODIFIED="1512179038425" TEXT="member-pointer"/>
|
||||
<node CREATED="1512179039109" ID="ID_746391928" MODIFIED="1512179057691">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Vorsicht bei
|
||||
</p>
|
||||
<p>
|
||||
mutually dependent templates
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<node CREATED="1512179063250" ID="ID_933791979" MODIFIED="1512179132703" TEXT="während der Instantiierung....">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
kann eines der Templates im Zyklus vorrübergehend als "incomplete" gelten.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1512179134400" ID="ID_1738202679" MODIFIED="1512179201014" TEXT="....kann die Metafunktion scheitern">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...wenn man dummerweise auf verschlungenen Pfaden
|
||||
</p>
|
||||
<p>
|
||||
genau in dieser Phase die Metafunktion anfragt,
|
||||
</p>
|
||||
<p>
|
||||
kann der betreffende Check stillschweigend scheitern.
|
||||
</p>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
<p>
|
||||
Konsequenz: man wählt dann z.B. eine subtil falsche Spezialisierung.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1512179201831" ID="ID_1577563892" MODIFIED="1512179219196" TEXT="tückischer Fehler">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1450488895106" ID="ID_1586185818" MODIFIED="1488423307351" TEXT="Threading">
|
||||
<node CREATED="1450488902049" ID="ID_1126260262" MODIFIED="1450488906100" TEXT="static init">
|
||||
|
|
|
|||
Loading…
Reference in a new issue