[boost] C++11 decltype/SFINAE puzzler

133 views
Skip to first unread message

Eric Niebler

unread,
Jul 6, 2012, 3:41:14 PM7/6/12
to Boost mailing list
I'm running into a usability problem with the extended SFINAE rules that
could effect best practice for all new C++ libraries. I'm hoping some
clever person in the Boost community can help me.

I find that in highly generic code, using decltype to define the return
type of functions is very handy. Dave A. suggested a RETURNS macro to
simplify this idiom:

<https://groups.google.com/d/msg/boost-devel-archive/OzJ5Ft3pSnU/b_Ter9bgNqAJ>

It's used like this:

template<typename T>
auto foo( T && t ) RETURNS( some-expression-using-t );

This is great and I use it a lot. Now, imagine you have a library of
such functions calling other such functions: function A calls B calls
... calls Y, calls Z, all of which use decltype to declare their return
types. Now, the user calls A passing a variable t for which the
expression in Z's return type calculation makes no sense. Extended
SFINAE kicks in, and Z simply disappears. Since Z has disappeared, the
expression in Y's return type makes no sense, so Y disappears, too. And
so on up the chain.

The end result is that the user is presented with an error like: "no
viable function A, template substitution failed." The user is given no
information about which function in the chain failed to compile, or why.
This is a serious usability problem!

So, my question is: what can be done about this? Can we get the benefit
of automatic type deduction in return types without causing SFINAE to
drop functions from the overload set? Is there another trick we can use
to report errors meaningfully when APIs are misused?

The only solution I can think of is turn all the free functions in the
chain into function objects and then using std::result_of to compute
return types for them instead of using decltype directly. That means
Dave's (very convenient!) RETURNS macro shouldn't be used, which is a
bummer. (I think it also means that we need a noexcept_of template that
computes the noexcept of an expression without causing the expression to
appear in the function declaration, but that depends on how
<http://llvm.org/bugs/show_bug.cgi?id=13286> gets resolved.)

All suggestions welcome,

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Marc Glisse

unread,
Jul 6, 2012, 3:54:42 PM7/6/12
to Boost mailing list
On Fri, 6 Jul 2012, Eric Niebler wrote:

> I'm running into a usability problem with the extended SFINAE rules that
> could effect best practice for all new C++ libraries. I'm hoping some
> clever person in the Boost community can help me.
>
> I find that in highly generic code, using decltype to define the return
> type of functions is very handy. Dave A. suggested a RETURNS macro to
> simplify this idiom:
>
> <https://groups.google.com/d/msg/boost-devel-archive/OzJ5Ft3pSnU/b_Ter9bgNqAJ>
>
> It's used like this:
>
> template<typename T>
> auto foo( T && t ) RETURNS( some-expression-using-t );
>
> This is great and I use it a lot. Now, imagine you have a library of
> such functions calling other such functions: function A calls B calls
> ... calls Y, calls Z, all of which use decltype to declare their return
> types. Now, the user calls A passing a variable t for which the
> expression in Z's return type calculation makes no sense. Extended
> SFINAE kicks in, and Z simply disappears. Since Z has disappeared, the
> expression in Y's return type makes no sense, so Y disappears, too. And
> so on up the chain.
>
> The end result is that the user is presented with an error like: "no
> viable function A, template substitution failed." The user is given no
> information about which function in the chain failed to compile, or why.
> This is a serious usability problem!

... in your compiler.

> All suggestions welcome,

Use g++.

template<class T> auto h(T x)->decltype(x.smurf()){return x.smurf();}
template<class T> auto g(T x)->decltype(h(x)){return h(x);}
template<class T> auto f(T x)->decltype(g(x)){return g(x);}
int main(){
f(3);
}

e.cc: In function 'int main()':
e.cc:5:6: error: no matching function for call to 'f(int)'
f(3);
^
e.cc:5:6: note: candidate is:
e.cc:3:24: note: template<class T> decltype (g(x)) f(T)
template<class T> auto f(T x)->decltype(g(x)){return g(x);}
^
e.cc:3:24: note: template argument deduction/substitution failed:
e.cc: In substitution of 'template<class T> decltype (g(x)) f(T) [with T =
int]':
e.cc:5:6: required from here
e.cc:3:24: error: no matching function for call to 'g(int&)'
e.cc:3:24: note: candidate is:
e.cc:2:24: note: template<class T> decltype (h(x)) g(T)
template<class T> auto g(T x)->decltype(h(x)){return h(x);}
^
e.cc:2:24: note: template argument deduction/substitution failed:
e.cc: In substitution of 'template<class T> decltype (h(x)) g(T) [with T =
int]':
e.cc:3:24: required by substitution of 'template<class T> decltype
(g(x)) f(T) [with T = int]'
e.cc:5:6: required from here
e.cc:2:24: error: no matching function for call to 'h(int&)'
e.cc:2:24: note: candidate is:
e.cc:1:24: note: template<class T> decltype (x.smurf()) h(T)
template<class T> auto h(T x)->decltype(x.smurf()){return x.smurf();}
^
e.cc:1:24: note: template argument deduction/substitution failed:
e.cc: In substitution of 'template<class T> decltype (x.smurf()) h(T)
[with T = int]':
e.cc:2:24: required by substitution of 'template<class T> decltype
(h(x)) g(T) [with T = int]'
e.cc:3:24: required by substitution of 'template<class T> decltype
(g(x)) f(T) [with T = int]'
e.cc:5:6: required from here
e.cc:1:24: error: request for member 'smurf' in 'x', which is of non-class
type 'int'

--
Marc Glisse

Andrew Sutton

unread,
Jul 6, 2012, 4:15:41 PM7/6/12
to bo...@lists.boost.org
> So, my question is: what can be done about this? Can we get the benefit
> of automatic type deduction in return types without causing SFINAE to
> drop functions from the overload set? Is there another trick we can use
> to report errors meaningfully when APIs are misused?

No!

Just kidding. I was actually wondering something very similar earlier
today. How can we intelligently document the causes of SFINAE-based
overloading errors? I don't have a good answer yet.

But I plugged in the problem you mentioned and checked it out with GCC
4.8 (compiled from trunk a month ago -- has it been so long?).


template <typename T>
auto h(T x) -> decltype(*x) { return *x; }

template <typename T>
auto g(T x) -> decltype(h(x)) { return h(x); }

template <typename T>
auto f(T x) -> decltype(g(x)) { return g(x); };

int main()
{
f(0);
}

And get this as output:

foo.cpp: In function ‘int main()’:
foo.cpp:22:6: error: no matching function for call to ‘f(int)’
f(0);
^
foo.cpp:22:6: note: candidate is:
foo.cpp:18:8: note: template<class T> decltype (g(x)) f(T)
auto f(T x) -> decltype(g(x)) { return g(x); };
^
foo.cpp:18:8: note: template argument deduction/substitution failed:
foo.cpp: In substitution of ‘template<class T> decltype (g(x)) f(T)
[with T = int]’:
foo.cpp:22:6: required from here
foo.cpp:18:8: error: no matching function for call to ‘g(int&)’
foo.cpp:18:8: note: candidate is:
foo.cpp:15:8: note: template<class T> decltype (h(x)) g(T)
auto g(T x) -> decltype(h(x)) { return h(x); }
^
foo.cpp:15:8: note: template argument deduction/substitution failed:
foo.cpp: In substitution of ‘template<class T> decltype (h(x)) g(T)
[with T = int]’:
foo.cpp:18:8: required by substitution of ‘template<class T>
decltype (g(x)) f(T) [with T = int]’
foo.cpp:22:6: required from here
foo.cpp:15:8: error: no matching function for call to ‘h(int&)’
foo.cpp:15:8: note: candidate is:
foo.cpp:12:8: note: template<class T> decltype (* x) h(T)
auto h(T x) -> decltype(*x) { return *x; }
^
foo.cpp:12:8: note: template argument deduction/substitution failed:
foo.cpp: In substitution of ‘template<class T> decltype (* x) h(T)
[with T = int]’:
foo.cpp:15:8: required by substitution of ‘template<class T>
decltype (h(x)) g(T) [with T = int]’
foo.cpp:18:8: required by substitution of ‘template<class T>
decltype (g(x)) f(T) [with T = int]’
foo.cpp:22:6: required from here
foo.cpp:12:27: error: invalid type argument of unary ‘*’ (have ‘int’)
auto h(T x) -> decltype(*x) { return *x; }


So GCC at least is documenting the root cause of the error. It would
still be nice to customize error reporting for certain classes of
failures in an API.

Andrew Sutton

unread,
Jul 6, 2012, 4:17:16 PM7/6/12
to bo...@lists.boost.org
> Use g++.
>
> template<class T> auto h(T x)->decltype(x.smurf()){return x.smurf();}
> template<class T> auto g(T x)->decltype(h(x)){return h(x);}
> template<class T> auto f(T x)->decltype(g(x)){return g(x);}

Apparently I should have updated Gmail before hitting send. Looks very
familiar :)

Eric Niebler

unread,
Jul 6, 2012, 4:23:18 PM7/6/12
to bo...@lists.boost.org
On 7/6/2012 12:54 PM, Marc Glisse wrote:
> On Fri, 6 Jul 2012, Eric Niebler wrote:
>> The end result is that the user is presented with an error like: "no
>> viable function A, template substitution failed." The user is given no
>> information about which function in the chain failed to compile, or why.
>> This is a serious usability problem!
>
> ... in your compiler.
>
>> All suggestions welcome,
>
> Use g++.
<snip informative backtrace>

Ah! Thanks. I'm using clang. I agree, it's a compiler QoI issue. I'll
follow up with them.

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Doug Gregor

unread,
Jul 6, 2012, 8:03:29 PM7/6/12
to bo...@lists.boost.org
On Fri, Jul 6, 2012 at 1:23 PM, Eric Niebler <er...@boostpro.com> wrote:
> On 7/6/2012 12:54 PM, Marc Glisse wrote:
>> On Fri, 6 Jul 2012, Eric Niebler wrote:
>>> The end result is that the user is presented with an error like: "no
>>> viable function A, template substitution failed." The user is given no
>>> information about which function in the chain failed to compile, or why.
>>> This is a serious usability problem!
>>
>> ... in your compiler.
>>
>>> All suggestions welcome,
>>
>> Use g++.
> <snip informative backtrace>
>
> Ah! Thanks. I'm using clang. I agree, it's a compiler QoI issue. I'll
> follow up with them.

FWIW, top-of-tree Clang produces:

t.cpp:12:3: error: no matching function for call to 'f'
f(0);
^
t.cpp:8:8: note: candidate template ignored: substitution failure
[with T = int]: no matching function for call to 'g'
auto f(T x) -> decltype(g(x)) { return g(x); };
^ ~

Which could probably still be improved, although I can't convince
myself that GCC 4.8 is doing us favors by producing the complete
SFINAE backtrace.

- Doug

Nathan Ridge

unread,
Jul 6, 2012, 8:43:23 PM7/6/12
to Boost Developers Mailing List

> From: doug....@gmail.com
> On Fri, Jul 6, 2012 at 1:23 PM, Eric Niebler <er...@boostpro.com> wrote:
> > On 7/6/2012 12:54 PM, Marc Glisse wrote:
> >> On Fri, 6 Jul 2012, Eric Niebler wrote:
> >>> The end result is that the user is presented with an error like: "no
> >>> viable function A, template substitution failed." The user is given no
> >>> information about which function in the chain failed to compile, or why.
> >>> This is a serious usability problem!
> >>
> >> ... in your compiler.
> >>
> >>> All suggestions welcome,
> >>
> >> Use g++.
> > <snip informative backtrace>
> >
> > Ah! Thanks. I'm using clang. I agree, it's a compiler QoI issue. I'll
> > follow up with them.
>
> FWIW, top-of-tree Clang produces:
>
> t.cpp:12:3: error: no matching function for call to 'f'
> f(0);
> ^
> t.cpp:8:8: note: candidate template ignored: substitution failure
> [with T = int]: no matching function for call to 'g'
> auto f(T x) -> decltype(g(x)) { return g(x); };
> ^ ~
>
> Which could probably still be improved, although I can't convince
> myself that GCC 4.8 is doing us favors by producing the complete
> SFINAE backtrace.

I think that as a matter of principle, more information is better than
less. The raw error message can relatively easily be parsed by a tool
that filters out undesired detail, but if the detail is not emitted in
the first place, it's gone.

Regards,
Nate

Eric Niebler

unread,
Jul 6, 2012, 9:17:24 PM7/6/12
to bo...@lists.boost.org
I'd like to second what Nate said. My experience is that not providing
the backtrace is a usability nightmare. To me, this is *exactly*
analogous to a template instantiation backtrace and should, IMO, be
handled exactly the same way. If you want to snip the middle of the
backtrace, fine, but show the start and the end (the deepest point, I
guess), and perhaps provide an option for dumping the complete thing.

My context is this: I'm rewriting proto in C++11. Entire expression tree
transforms are function call chains like this. To simply be told, "nope,
that transform can't be applied" but not why is maddening.

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Nathan Ridge

unread,
Jul 6, 2012, 10:42:39 PM7/6/12
to Boost Developers Mailing List

<aside>

> I'm rewriting proto in C++11.

Nice!

</aside>

Regards,
Nate

Doug Gregor

unread,
Jul 9, 2012, 11:42:28 AM7/9/12
to bo...@lists.boost.org
The typical complaints about template error messages concern excess
verbosity rather than a lack of detail, so simply providing the full
backtrace in all cases is not necessarily helpful. A more nuanced
approach that provides this detail when it is interesting (e.g., only
one candidate needs it) would be ideal; failing that, some
command-line options to dial up the verbosity would allow experts to
get the information they need while not flooding the terminals of the
majority of C++ programmers.

This is certainly worth of a Clang bug: http://llvm.org/bugs/

- Doug

Felipe Magno de Almeida

unread,
Jul 9, 2012, 11:58:45 AM7/9/12
to bo...@lists.boost.org
On Fri, Jul 6, 2012 at 10:17 PM, Eric Niebler <er...@boostpro.com> wrote:
>

[snip]

> I'd like to second what Nate said. My experience is that not providing
> the backtrace is a usability nightmare. To me, this is *exactly*
> analogous to a template instantiation backtrace and should, IMO, be
> handled exactly the same way. If you want to snip the middle of the
> backtrace, fine, but show the start and the end (the deepest point, I
> guess), and perhaps provide an option for dumping the complete thing.

+1

I have this problem all the time with skipped template instantiations
in error messages in GCC. Sometimes I just can't know which code
causes the problem because its instantiation isn't listed in the error
message. This is *very annoying* and I just surrender and try with
MSVC to find the root cause of the problem. It is not always the
leaf, nor is always the first instantation as well.

[snip]

> --
> Eric Niebler
> BoostPro Computing
> http://www.boostpro.com

Regards,
--
Felipe Magno de Almeida

Eric Niebler

unread,
Jul 9, 2012, 3:32:29 PM7/9/12
to bo...@lists.boost.org
On 7/9/2012 8:42 AM, Doug Gregor wrote:
> On Fri, Jul 6, 2012 at 6:17 PM, Eric Niebler <er...@boostpro.com> wrote:
>> I'd like to second what Nate said. My experience is that not providing
>> the backtrace is a usability nightmare. To me, this is *exactly*
>> analogous to a template instantiation backtrace and should, IMO, be
>> handled exactly the same way. If you want to snip the middle of the
>> backtrace, fine, but show the start and the end (the deepest point, I
>> guess), and perhaps provide an option for dumping the complete thing.
>
> The typical complaints about template error messages concern excess
> verbosity rather than a lack of detail, so simply providing the full
> backtrace in all cases is not necessarily helpful.

OK, agreed.

> A more nuanced
> approach that provides this detail when it is interesting (e.g., only
> one candidate needs it)

Not sure what you mean by "only one candidate needs it". I think there
will be a challenge reporting these failures when there are overload
sets in the call tree. You probably don't want to report the reason for
failure for every overload at every level of the tree -- at least not by
default.

> would be ideal; failing that, some
> command-line options to dial up the verbosity would allow experts to
> get the information they need while not flooding the terminals of the
> majority of C++ programmers.

Yes, please. We don't want to have to tell people to try compiling with
gcc if they don't understand clang's errors. :-P

> This is certainly worth of a Clang bug: http://llvm.org/bugs/

http://llvm.org/bugs/show_bug.cgi?id=13309

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Mathias Gaunard

unread,
Jul 17, 2012, 11:02:41 AM7/17/12
to bo...@lists.boost.org
On 09/07/2012 21:32, Eric Niebler wrote:

> Yes, please. We don't want to have to tell people to try compiling with
> gcc if they don't understand clang's errors. :-P

I've already been doing just that for a while. Clang error messages are
not as good as they claim.

The macro expansion, in particular, can easily lead to ridiculously
verbose error messages.

Mathias Gaunard

unread,
Jul 17, 2012, 11:05:09 AM7/17/12
to bo...@lists.boost.org
On 09/07/2012 17:58, Felipe Magno de Almeida wrote:

> I have this problem all the time with skipped template instantiations
> in error messages in GCC. Sometimes I just can't know which code
> causes the problem because its instantiation isn't listed in the error
> message. This is *very annoying* and I just surrender and try with
> MSVC to find the root cause of the problem. It is not always the
> leaf, nor is always the first instantation as well.

The next GCC version (4.8) supports the -ftemplate-backtrace-limit=0
option to disable the limit.

Clang supports it as well.

Eric Niebler

unread,
Aug 10, 2012, 6:58:09 PM8/10/12
to bo...@lists.boost.org, Andrew Sutton
On 7/6/2012 1:15 PM, Andrew Sutton wrote:
>> So, my question is: what can be done about this? Can we get the benefit
>> of automatic type deduction in return types without causing SFINAE to
>> drop functions from the overload set? Is there another trick we can use
>> to report errors meaningfully when APIs are misused?
>
> No!
>
> Just kidding. I was actually wondering something very similar earlier
> today. How can we intelligently document the causes of SFINAE-based
> overloading errors? I don't have a good answer yet.

I spent some time playing with this today. Attached is my best shot. The
basic idea is to change the RETURNS macro to -- in addition to declaring
the trailing return type and the function body -- also define an
implicit conversion to a function pointer. That gets selected as the
function called in the case that sfinae fails. That function returns a
sfinae_error that contains some useful information: the text of the
expression that failed to compile, the file and line number of the
sfinae failure, and the types of the function parameters (mangled,
unfortunately).

It's used like this:

struct S0 {
template<typename T>
auto operator()(T t) const RETURN( (t), t + 1 )
};

The first macro parameter is the function parameters. The second is the
expression to evaluate.

If all your function objects are declared this way, you can call them as
normal. But if you get a sfinae failure anywhere in the call chain, the
result of the call is a sfinae_error object with the goods.

Can anybody do better?
sfinae_error.cpp

paul Fultz

unread,
Aug 11, 2012, 9:23:04 PM8/11/12
to bo...@lists.boost.org

> I spent some time playing with this today. Attached is my best shot. The
> basic idea is to change the RETURNS macro to -- in addition to declaring
> the trailing return type and the function body -- also define an
> implicit conversion to a function pointer. That gets selected as the
> function called in the case that sfinae fails. That function returns a
> sfinae_error that contains some useful information: the text of the
> expression that failed to compile, the file and line number of the
> sfinae failure, and the types of the function parameters (mangled,
> unfortunately).
>

Unfortunately, this results in a run-time error instead of compile-time.

> Can anybody do better?

Attached is code that does present a compile-time error. It uses a function
adaptor called `sfinae_error`. This will force sfinae success on the expression
type dedcution by using a fail-through type. The fail through type doesn't
matter since we know the function call will produce a compile error either way.

So now, the `S0` class can be defined like this:

    struct S0_sfinae {
        template<typename T>
        auto operator()(T t) const RETURN( t + 1 )
    };

    typedef sfinae_error<S0_sfinae> S0;

Then when you try to call it like this:

    struct foo {};
    S2()(foo());

Clang will output the error inside of `sfinae_error` function adaptor with a
full backtrace. At the bottom it will say:

    note: candidate template ignored: substitution failure

          [with T = foo]: invalid operands to binary expression ('foo' and 'int')
        auto operator()(T t) const RETURN( t + 1 )

Which is what you want. Futhermore, this can be used even when there are
multiple overloads in the `S0` class.

sfinae_error.cpp

Eric Niebler

unread,
Aug 12, 2012, 10:50:57 PM8/12/12
to bo...@lists.boost.org
On 8/11/2012 6:23 PM, paul Fultz wrote:
>
>
>> I spent some time playing with this today. Attached is my best shot. The
>> basic idea is to change the RETURNS macro to -- in addition to declaring
>> the trailing return type and the function body -- also define an
>> implicit conversion to a function pointer. That gets selected as the
>> function called in the case that sfinae fails. That function returns a
>> sfinae_error that contains some useful information: the text of the
>> expression that failed to compile, the file and line number of the
>> sfinae failure, and the types of the function parameters (mangled,
>> unfortunately).
>>
> Unfortunately, this results in a run-time error instead of compile-time.

Right. It can be easily turned into a compile-time error by
static_assert'ing that the function's return type is not sfinae_error,
but the compiler error wouldn't be informative. You'd have to run to
code to see the error.

I was hoping to have a way to transport the error to an API boundary and
reporting it there, instead of presenting users with an imposing
template instantiation backtrace, but that might not be possible.

>> Can anybody do better?
>
> Attached is code that does present a compile-time error. It uses a function
> adaptor called `sfinae_error`. This will force sfinae success on the expression
> type dedcution by using a fail-through type. The fail through type doesn't
> matter since we know the function call will produce a compile error either way.
>
> So now, the `S0` class can be defined like this:
>
> struct S0_sfinae {
> template<typename T>
> auto operator()(T t) const RETURN( t + 1 )
> };
>
> typedef sfinae_error<S0_sfinae> S0;
>
> Then when you try to call it like this:
>
> struct foo {};
> S2()(foo());
>
> Clang will output the error inside of `sfinae_error` function adaptor with a
> full backtrace. At the bottom it will say:
>
> note: candidate template ignored: substitution failure
> [with T = foo]: invalid operands to binary expression ('foo' and 'int')
> auto operator()(T t) const RETURN( t + 1 )
>
> Which is what you want. Futhermore, this can be used even when there are
> multiple overloads in the `S0` class.

Great! And very simple. It doesn't move the error close to the API
boundary, but other than that, it fits the bill. Thanks. If I end up
using this technique in my code, I will credit you.

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

John Maddock

unread,
Aug 13, 2012, 3:46:48 AM8/13/12
to bo...@lists.boost.org
> Right. It can be easily turned into a compile-time error by
> static_assert'ing that the function's return type is not sfinae_error,
> but the compiler error wouldn't be informative. You'd have to run to
> code to see the error.
>
> I was hoping to have a way to transport the error to an API boundary and
> reporting it there, instead of presenting users with an imposing
> template instantiation backtrace, but that might not be possible.

I haven't followed this discussion, but you can move static_assert's up the
call stack by moving the condition into an enable/disable_if, then the
compiler simply doesn't find the function and you get the error "sooner".

But you've probably considered all this already yours, John.

Eric Niebler

unread,
Aug 13, 2012, 2:36:47 PM8/13/12
to bo...@lists.boost.org
On 8/13/2012 12:46 AM, John Maddock wrote:
>> Right. It can be easily turned into a compile-time error by
>> static_assert'ing that the function's return type is not sfinae_error,
>> but the compiler error wouldn't be informative. You'd have to run to
>> code to see the error.
>>
>> I was hoping to have a way to transport the error to an API boundary and
>> reporting it there, instead of presenting users with an imposing
>> template instantiation backtrace, but that might not be possible.
>
> I haven't followed this discussion, but you can move static_assert's up
> the call stack by moving the condition into an enable/disable_if, then
> the compiler simply doesn't find the function and you get the error
> "sooner".
>
> But you've probably considered all this already yours, John.

John, what you're describing is precisely what we're trying to avoid: a
mysterious "cannot find function" error because the function has been
SFINAE'd out ... but where and why? The programmer needs better feedback
than that.

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Eric Niebler

unread,
Aug 13, 2012, 4:03:33 PM8/13/12
to bo...@lists.boost.org, Andrew Sutton
On 8/12/2012 7:50 PM, Eric Niebler wrote:
> Great! And very simple. It doesn't move the error close to the API
> boundary, but other than that, it fits the bill. Thanks. If I end up
> using this technique in my code, I will credit you.

I think I have a very nice solution now. It combines my earlier approach
with with Paul Fritz's. See the attached.

You define your callables with the RETURNS macro and a try_call function
object wrapper, like this:

struct S0
{
template<typename T>
auto operator()(T t) const RETURN( t + 1 )
};

struct S1
{
template<typename T>
auto operator()(T t) const RETURN( try_call<S0>()(t) )
};

struct S2
{
template<typename T>
auto operator()(T t) const RETURN( try_call<S1>()(t) )
};

Then, you simply invoke your function object:

auto i = S2()(32); // ok
auto x = S2()(foo()); // compile-time error

The compile error is transported to the API boundary. The result is a
very short and precise error message about the cause of the failure.
Clang gives this:

> $ /usr/local/bin/clang++ -std=gnu++11 sfinae_error.cpp
> sfinae_error.cpp:28:26: error: no matching function for call to object of type 'S0'
> typedef decltype(std::declval<Fun>()(std::declval<Args>()...)) type;
> ^~~~~~~~~~~~~~~~~~~
> sfinae_error.cpp:91:14: note: in instantiation of member function 'sfinae_error<S0 (foo &)>::what'
> requested here
> auto x = S2()(foo());
> ^
> sfinae_error.cpp:70:10: note: candidate template ignored: substitution failure [with T = foo]:
> invalid operands to binary expression ('foo' and 'int')
> auto operator()(T t) const RETURN( t + 1 )
> ^ ~~~~~~~~~~~~~~~
> 1 error generated.

Note that no information about the intermediate calls (S2, S1) shows up
in the backtrace. Just the error you care about.

GCC-4.7 gives a similarly terse error message. This, I think, is what
I've been looking for.
sfinae_error.cpp

Eric Niebler

unread,
Aug 14, 2012, 3:25:16 PM8/14/12
to Andrew Sutton, bo...@lists.boost.org
(Andrew, a specific question for you below...)

(Moderators: can you see why this msg from Andrew never reached the list
(which he cc'ed))?

On 8/14/2012 8:39 AM, Andrew Sutton wrote:
>> GCC-4.7 gives a similarly terse error message. This, I think, is what
>> I've been looking for.
>
> That's neat. Bravo!

I've refined this idea. It now avoids cascading errors by detecting when
a sfinae_error is passed as an argument to another function. (For the
Haskell-ers out there, that makes it like the Either monad.) You can
also get the full backtrace by uncommenting one line of code.

IMO, this is now approaching something of real value. My personal
experience using this with Proto-11 is that it dramatically improves
error messages. I can also easily imagine how this can be used to
implement a very nice concept-checking library for C++11 that
approximates auto-concepts. Andrew, any thoughts on this? You've done
far more work in this space than I have.

I'd also like to know *why* this works, and indeed if it's guaranteed
to. In particular, is it guaranteed that the virtual what() member of
the sfinae_error class is instantiated at the outermost call site and
not elsewhere (thereby eliminating the deep template back-trace)? I
really just stumbled on this technique by accident, and I don't know how
thin the ice is here. Both clang-trunk and gcc-4.7 think it's ok.

(I'd also like to apologize to Paul Fultz for spelling his name wrong in
an earlier message.)
sfinae_error.cpp

Andrew Sutton

unread,
Aug 14, 2012, 4:12:00 PM8/14/12
to bo...@lists.boost.org, Andrew Sutton
> I'd also like to know *why* this works, and indeed if it's guaranteed
> to. In particular, is it guaranteed that the virtual what() member of
> the sfinae_error class is instantiated at the outermost call site and
> not elsewhere (thereby eliminating the deep template back-trace)? I
> really just stumbled on this technique by accident, and I don't know how
> thin the ice is here. Both clang-trunk and gcc-4.7 think it's ok.

I'll wager an explanation... maybe somebody will be able to refine it.

Because what() is virtual, it needs to build the vtable for
sfinae_error. However, because what() isn't actually used in the
program, it is instantiated at the very end of translation. Basically,
the point of instantiation is at the top-level, at the end of the
translation unit. There's no template instantiation stack to unwind.

Comment out the "virtual" keyword in front of what(). The program
should actually compile. The try_call doesn't actually force a
compilation error, it suppresses it. This only works because
sfinae_error happens to be polymorphic.

Using unused virtual functions to defer instantiation traps is
incredibly clever... If my understanding is correct :)

> IMO, this is now approaching something of real value. My personal
> experience using this with Proto-11 is that it dramatically improves
> error messages. I can also easily imagine how this can be used to
> implement a very nice concept-checking library for C++11 that
> approximates auto-concepts. Andrew, any thoughts on this? You've done
> far more work in this space than I have.

I want to say yes, but I'm not sure. What's really needed is a
combination of this and static_assert. I want compilation to stop
*and* I want brief errors. Hmm... Let me get back to you on this.

Andrew Sutton

unread,
Aug 14, 2012, 5:19:32 PM8/14/12
to bo...@lists.boost.org, Andrew Sutton
>> IMO, this is now approaching something of real value. My personal
>> experience using this with Proto-11 is that it dramatically improves
>> error messages. I can also easily imagine how this can be used to
>> implement a very nice concept-checking library for C++11 that
>> approximates auto-concepts. Andrew, any thoughts on this? You've done
>> far more work in this space than I have.
>
> I want to say yes, but I'm not sure. What's really needed is a
> combination of this and static_assert. I want compilation to stop
> *and* I want brief errors. Hmm... Let me get back to you on this.

I tinkered for a little bit, and couldn't get what I wanted. The
problem, from the perspective of general concept checking, is that I
want compilation to fail when a substitution failure would happen. For
example:

template <typename I, typename T>
I find(I first, I last, const T& value) {
static_assert (Input_iterator<I>(), "");
while (first != last && *first != value) ++first;
}

If a substitution of I triggers the assertion, then (ideally), we
would stop instantiating the body and emit an appropriate message.
What actually happens is that a diagnostic is reported, then
instantiation continues, generating whatever errors I can expect from
the expressions in the algorithm (e.g., no operator*). So we get
redundancy. If the errors occur in nested instantiations of those
expressions, we get template spew.

The sfinae_error technique is somewhat different. My characterization
is that it is useful for shallow logging of substitution errors, but
it won't help here. You could probably force a "logged failure" in
place of the static assert, but you'd still get all of the other
errors from body of the template.

You could make this technique work if you were willing to write all of
your algorithms as function objects, and guard all of your calls, but
that seems a little intrusive.

It's a good trick, but I don't think it scales.

Eric Niebler

unread,
Aug 14, 2012, 5:28:52 PM8/14/12
to bo...@lists.boost.org
On 8/14/2012 1:12 PM, Andrew Sutton wrote:
>> I'd also like to know *why* this works, and indeed if it's guaranteed
>> to. In particular, is it guaranteed that the virtual what() member of
>> the sfinae_error class is instantiated at the outermost call site and
>> not elsewhere (thereby eliminating the deep template back-trace)? I
>> really just stumbled on this technique by accident, and I don't know how
>> thin the ice is here. Both clang-trunk and gcc-4.7 think it's ok.
>
> I'll wager an explanation... maybe somebody will be able to refine it.
>
> Because what() is virtual, it needs to build the vtable for
> sfinae_error. However, because what() isn't actually used in the
> program, it is instantiated at the very end of translation. Basically,
> the point of instantiation is at the top-level, at the end of the
> translation unit. There's no template instantiation stack to unwind.
>
> Comment out the "virtual" keyword in front of what(). The program
> should actually compile. The try_call doesn't actually force a
> compilation error, it suppresses it. This only works because
> sfinae_error happens to be polymorphic.
>
> Using unused virtual functions to defer instantiation traps is
> incredibly clever... If my understanding is correct :)

The technique also works if the what() function is replaced with a
non-virtual destructor. So I don't think "virtual" is the magic
ingredient here.


>> IMO, this is now approaching something of real value. My personal
>> experience using this with Proto-11 is that it dramatically improves
>> error messages. I can also easily imagine how this can be used to
>> implement a very nice concept-checking library for C++11 that
>> approximates auto-concepts. Andrew, any thoughts on this? You've done
>> far more work in this space than I have.
>
> I want to say yes, but I'm not sure. What's really needed is a
> combination of this and static_assert. I want compilation to stop
> *and* I want brief errors. Hmm... Let me get back to you on this.

Couldn't you avoid generating the error in sfinae_error and instead
static_assert that a given function call doesn't have a sfinae_error
return type?

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Eric Niebler

unread,
Aug 14, 2012, 5:40:52 PM8/14/12
to bo...@lists.boost.org
To avoid redundant errors, you must dispatch to an empty implementation
on concept check failure:

template <typename I, typename T>
I find_impl(std::true_type, I first, I last, const T& value) {
while (first != last && *first != value) ++first;
}

template <typename I, typename T>
I find_impl(std::false_type, I first, I last, const T& value) {
static_assert (Input_iterator<I>(), "");
}

template <typename I, typename T>
I find(I first, I last, const T& value) {
find_impl(Input_iterator<I>(), first, last, value);
}

> The sfinae_error technique is somewhat different. My characterization
> is that it is useful for shallow logging of substitution errors, but
> it won't help here. You could probably force a "logged failure" in
> place of the static assert, but you'd still get all of the other
> errors from body of the template.

Not if you use the trick above.

> You could make this technique work if you were willing to write all of
> your algorithms as function objects,

If you're like me, you write function objects anyway because you prefer
first-class functions. :-)

> and guard all of your calls, but
> that seems a little intrusive.

You only have the guard the calls that add constraints. Other calls will
simply propagate sfinae_error and don't need to be guarded. (In my
example, S2 doesn't need to guard the call to S1, but S1 must guard S0
because it adds an Addable constraint.) Also, you can guard your
function object once by defining it with try_call_wrapper, so it doesn't
need to be guarded everywhere.

> It's a good trick, but I don't think it scales.

Just apply more force. :-)

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Ahmed Charles

unread,
Aug 14, 2012, 8:02:08 PM8/14/12
to bo...@lists.boost.org, Andrew Sutton
> I tinkered for a little bit, and couldn't get what I wanted. The
> problem, from the perspective of general concept checking, is that I
> want compilation to fail when a substitution failure would happen. For
> example:
>
> template <typename I, typename T>
> I find(I first, I last, const T& value) {
> static_assert (Input_iterator<I>(), "");
> while (first != last && *first != value) ++first;
> }
This technique comes closer, but you still get errors messages for a missing copy constructor... I think.
template <typename I, typename T>
I find_impl(I first, I last, const T& value, true_type) {
while (first != last && *first != value) ++first;}
template <typename I, typename T>
I find_impl(I first, I last, const T& value, false_type) {
return first;}
template <typename I, typename T>
I find(I first, I last, const T& value) { static_assert (Input_iterator<I>(), "");
return find_impl(first, last, value, std::integral_constant<bool, Input_iterator<I>()>());

Andrew Sutton

unread,
Aug 16, 2012, 9:38:45 AM8/16/12
to bo...@lists.boost.org
>> Using unused virtual functions to defer instantiation traps is
>> incredibly clever... If my understanding is correct :)
>
> The technique also works if the what() function is replaced with a
> non-virtual destructor. So I don't think "virtual" is the magic
> ingredient here.

Shoot. That sounded like such a reasonable explanation, too.

> Couldn't you avoid generating the error in sfinae_error and instead
> static_assert that a given function call doesn't have a sfinae_error
> return type?

You could (that's basically how Origin's concept checking stuff
works), but you'd still have the problem that every expression in the
constrained template will still be instantiated and type-checked so
you'll get errors.

Andrew Sutton

unread,
Aug 16, 2012, 9:44:09 AM8/16/12
to bo...@lists.boost.org
> To avoid redundant errors, you must dispatch to an empty implementation
> on concept check failure:
>
> template <typename I, typename T>
> I find_impl(std::true_type, I first, I last, const T& value) {
> while (first != last && *first != value) ++first;
> }
>
> template <typename I, typename T>
> I find_impl(std::false_type, I first, I last, const T& value) {
> static_assert (Input_iterator<I>(), "");
> }
>
> template <typename I, typename T>
> I find(I first, I last, const T& value) {
> find_impl(Input_iterator<I>(), first, last, value);
> }
>
> Not if you use the trick above.

Interesting... That looks like it will work. Do you still need the
sfinae_error class to get shallow errors? I'm guessing not in these
cases.

> You only have the guard the calls that add constraints. Other calls will
> simply propagate sfinae_error and don't need to be guarded. (In my
> example, S2 doesn't need to guard the call to S1, but S1 must guard S0
> because it adds an Addable constraint.) Also, you can guard your
> function object once by defining it with try_call_wrapper, so it doesn't
> need to be guarded everywhere.
>
> Just apply more force. :-)

Indeed :)

Side note: sfinae_error is a bad name in a worse way than how "PIN
number" is redundant. It's not an error, but it is.
substitution_failure or subst_failure might be better choices.

Eric Niebler

unread,
Aug 16, 2012, 12:01:40 PM8/16/12
to bo...@lists.boost.org
On 8/16/2012 6:44 AM, Andrew Sutton wrote:
>> To avoid redundant errors, you must dispatch to an empty implementation
>> on concept check failure:
>>
>> template <typename I, typename T>
>> I find_impl(std::true_type, I first, I last, const T& value) {
>> while (first != last && *first != value) ++first;
>> }
>>
>> template <typename I, typename T>
>> I find_impl(std::false_type, I first, I last, const T& value) {
>> static_assert (Input_iterator<I>(), "");
>> }
>>
>> template <typename I, typename T>
>> I find(I first, I last, const T& value) {
>> find_impl(Input_iterator<I>(), first, last, value);
>> }
>>
>> Not if you use the trick above.
>
> Interesting... That looks like it will work.

It should. It's an old trick I use in a bunch of places. I even wrote
about it here:

http://cpp-next.com/archive/2010/09/expressive-c-why-template-errors-suck-and-what-you-can-do-about-it/

(See the section "Avoid Follow-on Errors")

FWIW, the code I sent above can be improved. The static_assert should be
in find(), not find_impl. That will remove one frame of the stack
backtrace. And I think the find_impl that takes false_type only needs to
be forward-declared. That way you don't need to mock up a return call
that will never be used.

> Do you still need the
> sfinae_error class to get shallow errors? I'm guessing not in these
> cases.

Depends. If you already have an easy way to implement your concept
classes so that checking against types that don't model the concept
doesn't cause a hard error, then you can forget about the try_call
wrapper. Otherwise, it's still useful to you, even though you wouldn't
be making use of the short error reporting mechanism.

>> You only have the guard the calls that add constraints. Other calls will
>> simply propagate sfinae_error and don't need to be guarded. (In my
>> example, S2 doesn't need to guard the call to S1, but S1 must guard S0
>> because it adds an Addable constraint.) Also, you can guard your
>> function object once by defining it with try_call_wrapper, so it doesn't
>> need to be guarded everywhere.
>>
>> Just apply more force. :-)
>
> Indeed :)
>
> Side note: sfinae_error is a bad name in a worse way than how "PIN
> number" is redundant. It's not an error, but it is.
> substitution_failure or subst_failure might be better choices.

You're right. Thanks.

--
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Reply all
Reply to author
Forward
0 new messages