Asked  7 Months ago    Answers:  5   Viewed   24 times

I watched Walter Brown's talk at Cppcon14 about modern template programming (Part I, Part II) where he presented his void_t SFINAE technique.

Given a simple variable template that evaluates to void if all template arguments are well formed:

template< class ... > using void_t = void;

and the following trait that checks for the existence of a member variable called member:

template< class , class = void >
struct has_member : std::false_type
{ };

// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };

I tried to understand why and how this works. Therefore a tiny example:

class A {
    int member;

class B {

static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );

1. has_member< A >

  • has_member< A , void_t< decltype( A::member ) > >
    • A::member exists
    • decltype( A::member ) is well-formed
    • void_t<> is valid and evaluates to void
  • has_member< A , void > and therefore it chooses the specialized template
  • has_member< T , void > and evaluates to true_type

2. has_member< B >

  • has_member< B , void_t< decltype( B::member ) > >
    • B::member does not exist
    • decltype( B::member ) is ill-formed and fails silently (sfinae)
    • has_member< B , expression-sfinae > so this template is discarded
  • compiler finds has_member< B , class = void > with void as default argument
  • has_member< B > evaluates to false_type

1. Is my understanding of this correct?
2. Walter Brown states that the default argument has to be the exact same type as the one used in void_t for it to work. Why is that? (I don't see why this types need to match, doesn't just any default type does the job?)



1. Primary Class Template

When you write has_member<A>::value, the compiler looks up the name has_member and finds the primary class template, that is, this declaration:

template< class , class = void >
struct has_member;

(In the OP, that's written as a definition.)

The template argument list <A> is compared to the template parameter list of this primary template. Since the primary template has two parameters, but you only supplied one, the remaining parameter is defaulted to the default template argument: void. It's as if you had written has_member<A, void>::value.

2. Specialized Class Template

Now, the template parameter list is compared against any specializations of the template has_member. Only if no specialization matches, the definition of the primary template is used as a fall-back. So the partial specialization is taken into account:

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

The compiler tries to match the template arguments A, void with the patterns defined in the partial specialization: T and void_t<..> one by one. First, template argument deduction is performed. The partial specialization above is still a template with template-parameters that need to be "filled" by arguments.

The first pattern T, allows the compiler to deduce the template-parameter T. This is a trivial deduction, but consider a pattern like T const&, where we could still deduce T. For the pattern T and the template argument A, we deduce T to be A.

In the second pattern void_t< decltype( T::member ) >, the template-parameter T appears in a context where it cannot be deduced from any template argument.

There are two reasons for this:

  • The expression inside decltype is explicitly excluded from template argument deduction. I guess this is because it can be arbitrarily complex.

  • Even if we used a pattern without decltype like void_t< T >, then the deduction of T happens on the resolved alias template. That is, we resolve the alias template and later try to deduce the type T from the resulting pattern. The resulting pattern, however, is void, which is not dependent on T and therefore does not allow us to find a specific type for T. This is similar to the mathematical problem of trying to invert a constant function (in the mathematical sense of those terms).

Template argument deduction is finished(*), now the deduced template arguments are substituted. This creates a specialization that looks like this:

struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };

The type void_t< decltype( A::member ) > can now be evaluated. It is well-formed after substitution, hence, no Substitution Failure occurs. We get:

struct has_member<A, void> : true_type
{ };

3. Choice

Now, we can compare the template parameter list of this specialization with the template arguments supplied to the original has_member<A>::value. Both types match exactly, so this partial specialization is chosen.

On the other hand, when we define the template as:

template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

We end up with the same specialization:

struct has_member<A, void> : true_type
{ };

but our template argument list for has_member<A>::value now is <A, int>. The arguments do not match the parameters of the specialization, and the primary template is chosen as a fall-back.

(*) The Standard, IMHO confusingly, includes the substitution process and the matching of explicitly specified template arguments in the template argument deduction process. For example (post-N4296) [temp.class.spec.match]/2:

A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list.

But this does not just mean that all template-parameters of the partial specialization have to be deduced; it also means that substitution must succeed and (as it seems?) the template arguments have to match the (substituted) template parameters of the partial specialization. Note that I'm not completely aware of where the Standard specifies the comparison between the substituted argument list and the supplied argument list.

Tuesday, June 1, 2021
answered 7 Months ago

Trac and Redmine both support integration with Git. It looks more or less exactly the same as the Subversion support. The bug tracker follows one repo as the benevolent dictator repo, it doesn't have to care about all the other clones around the place.

One thing I do think is worth mentioning is that any bug tracker needs to support git branches properly. Working on branches is such an important part of the Git methodology, it needs to be supported in the bug tracker. Redmine can do this through a patch, but last I looked (about a month ago), it wasn't in the main source tree (you could only follow master).

Other useful features would be a graphical representation of how branches are created and merged, similar to how gitk looks. I don't know of any bug tracker that does this kind of visualisation.

EDIT by Corey Trager. I copy/pasted @Squelch's answer here (I upvoted @Squelch too):

Due to the distributed nature of Git against the centralized nature of SVN, it is quite possible for every user or copy of the repository to have different branches. The exisitnig trackers typically have a local copy of the repository that is used as a central reference ("benevolent dictator") that can be regarded as the working copy for all users.

It is quite feasible for users to have a different branch structure in their local copy from that of the tracker. They might choose to keep some private, pull only the branches from the remote that they are interested in, or push a new branch to the remote (tracker). Users can even share branches between themselves that the remote may never see.

The bug tracker can really only reference repositories it has access to. Commonly this is local to the tracker, but it is also possible to pull from repositories remote to the tracker, and far harder to manage. If it is accessing a remote, it can only track branches that it has knowledge of, and there is not really a method of initiating this task apart from a scheduled task. This also assumes that users are serving their local copy too.

As you have already noted, a scheduled task, or an event hook can be used to update the tracker using the commit log for details. These details can then be matched to the tracker issues for viewing as required and noted above.

In short, the tracker will typically see whatever changes are made on the branches it currently has access to. With a hook these changes are seen immediately including the creation of a new branch. It will not see or track changes made to users (offline) repositories until they push those changes.

END OF @Squelch

Sunday, August 1, 2021
answered 4 Months ago

If you need to extend it to multiple overloads with fine grained control of overload rank, a common technique is to use tag dispatching.

template<int r>
struct rank : rank<r - 1> {};

struct rank<0> {};

template<typename T>
auto zero_impl(rank<0>) -> decltype(static_cast<T>(0)) {
    return static_cast<T>(0);

template<typename T>
auto zero_impl(rank<1>) ->decltype(T::zero()) {
    return T::zero();

template<typename T>
auto zero() { return zero_impl<T>(rank<10>{}); }

Derived to base conversions will prefer the closest base class. Which translates to calling the overload with the highest rank. Since that one will have the best implicit conversion sequence in the eyes of the compiler.

Saturday, August 7, 2021
answered 4 Months ago

Auto-completion is not available yet, it's planned for MySQL Workbench version 6.


Thursday, October 28, 2021
answered 1 Month ago

This is a variant of the bog-standard two-phase lookup question. For dependent function names,

  • Unqualified lookup considers only the template definition context
  • Argument-dependent lookup considers both the template definition context and the template instantiation context.

For your second case, unqualified lookup in the template definition context finds nothing, and there's no ADL for const char *.

Wednesday, November 17, 2021
answered 2 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :