
On Fri, 01 Jul 2011 10:40:37 -0400, Lorenzo Caminiti wrote:
On Fri, Jul 1, 2011 at 2:09 AM, Paul Mensonides <pmenso57@comcast.net> wrote:
IS_VARIADIC( int x, int& y ) // 0 IS_VARIADIC( (int x)(int& y) ) // 1 IS_VARIADIC( (...) xyz ) // 1
For your particular case, wouldn't that be sufficient?
Yes, in fact IS_VARIADIC is the macro Boost.Local currently uses. See my rudimentary implementation at: http://svn.boost.org/svn/boost/sandbox/local/boost/local/aux_/ preprocessor/variadic/is.hpp
This implementation is fine for Boost.Local but I have not tested in general and it might impose unacceptable limitation (like the empty issue we discussed also while discussing VMD).
IS_VARIADIC can be defined without the issues associated with IS_EMPTY. The problem with the mythical IS_TUPLE is that it requires both IS_VARIADIC *and* IS_EMPTY and that is where the problems come in.
Regardless, I actually believe that the (1) case above is actually a large step *backwards*. It's worse, not better, because it's trying (and failing) to be the underlying language and introducing a bunch of gotchas in the attempt.
I can only say that (i) I agree with you BUT (ii) my library users (my customers) wanted (1) very badly so I really have no choice.
I know that in your particular case, it is already too late given that it is a deployed interface. However, the better strategy would be to let the third party make the (mistake of creating the) mapping. Those responsible for writing code should do it the right way as far as can be known at the time. For a library author, that means *not* providing poor interfaces. For a library, this is where the responsibility stops, and clients can do whatever they want--good or bad. They *should* understand the issues that result from what they do and do accordingly, but that is still up to them. In the case of the pp-lib, I'm the primary author and maintainer. Obviously, it's an open-source, multi-developer project, so others can make changes, but I will certainly push back against adding interfaces to the library which I believe to be bad interfaces. For the three interfaces in question: 0) IS_NULLARY, IS_UNARY, IS_BINARY, ... = good interfaces (< C99 || < C+ +0x) 1) IS_VARIADIC = good interface (>= C99 || >= C++0x) 2) IS_EMPTY = good interface only in a particular domain of applicability 3) IS_TUPLE = bad interface The IS_TUPLE scenario, even if it wasn't for the serious input constraints required by any IS_EMPTY implementation, is fundamentally broken in the larger context. IS_ARRAY and IS_LIST would have even more constraints. IS_SEQ would have similar constraints. But the root problem with all of these is the massive ambiguity that results from a typeless system. Is (a) a one-element tuple or a one-element sequence? Is (a, b) a two- element tuple or a one-element variadic sequence? Is (3, (a, b, c)) a three-element array, a two-element tuple, or a one-element variadic sequence? These are all questions that cannot be answered without context (i.e. meaning) that is not present in the structural form of the input. Further, all of these various forms arise naturally in various contexts--these aren't corner cases. Aside from all implementability issues, one could say: IS_ARRAY ( (3, (a, b, c)) ) // 1 IS_SEQ ( (3, (a, b, c)) ) // 1 IS_TUPLE ( (3, (a, b, c)) ) // 1 IS_ARRAY ( abc ) // 0 IS_SEQ ( abc ) // 0 IS_TUPLE ( abc ) // 0 ...but, because of the ambiguity (and, in some cases, lack of implementability), you cannot dispatch on arrays vs. lists vs. sequences vs. tuples. Instead, you can only dispatch on "data structure which I am already expecting" vs. "not a data structure" and that is where IS_VARIADIC comes in because that already allows the distinction. For example, let's say you have an interface MACRO(a) where 'a' can be either a single element of whatever domain you're targeting or it can be a tuple of elements for whatever domain you're targeting. (For the sake of argument, let's say that domain is integers.) So, both MACRO(1) and MACRO((1, 2, 3)) are allowed. What isn't allowed is something like MACRO ((1, 2, 3) 4). If, for the particular domain, that (or something like it) should be allowed, then that is a different, domain-specific data structure which is outside the purview of the pp-lib. However, if it isn't allowed (as is typical), IS_VARIADIC already distinguishes between the two allowed scenarios and suffers none of the issues associated with detecting the difference between (1, 2, 3) and (1, 2, 3) 4. Another example, which *may* be valid in a particular domain, is initialization of a "real" data structure from variadic data. In that case, one might have an interface that accepts any of MACRO(1), MACRO(1, 2), MACRO(1, 2, 3), etc.. However, that should be immediately converted to a real data structure (e.g. tuple or sequence) and subsequently processed. However, whether that is sound is highly dependent on the domain of elements. For your case, that domain covers C++ types. There we already know that we have "open" commas (at least as far as the preprocessor is concerned), and we already know that variadic data is not a suitable transmission mechanism for these without encoding. Note that I'm not implying that there are currently better data structures in the pp-lib as pp-lib sequences don't allow variadic elements, but that is where the work, if it's going to be done, should be done. So, as an example, in the particular case of a list of types, the interface should be MACRO((T)(U)(V)) for any type T, U, and V implemented on top of variadic sequence algorithms. A client can then do something like MACRO (VARIADIC_TO_SEQ(a, b, c)) if they so (unfortunately) choose. Further, they can define their own MY_MACRO that does this: #define MY_MACRO(...) MACRO(VARIADIC_TO_SEQ(__VA_ARGS__)) The fundamental issue here is really whether making macros invocations look like they are not macro invocations is a worthwhile goal as opposed to an incidental and superficial similarity. IMO, making macro invocations look like regular function calls--on purpose, rather than incidentally--is *never*, under *any* circumstances, a worthwhile goal. That applies to things like the above, but also the even worse scenarios where people write macros where they bend over backward (e.g. do loop wrappers) to force a semicolon outside the invocation. #define MACRO(x) \ do { \ /* whatever */ \ } while (false) \ /**/ void f() { int x = 10; MACRO(x); } Pure evil. Bad design. Leads to all of the problems that have produced widespread over-generalizations such as "macros are evil." Macros aren't evil, the above is evil. As is *any* motivation whatsoever to make a macro invocation look like the underlying language. The macro replacement mechanism (really, the preprocessor as a whole) is a different language whose input is the source code and whose output is the underlying language. In a sense, it is no different than a DSEL. A DSEL is a different language embedded in a host language that (typically) better describes the particular domain than the host language does. Why is that considered bad in the context of the preprocessor? Why the constant resistance to treating it as such--especially given the success of the DSEL concept? Regards, Paul Mensonides