2

I realize this is a pretty basic question, but I just want to confirm that I'm using std::enable_if correctly because I'm a little uncertain what the "correct" error messages should be when attempting to call a disabled function.

Consider the following program (link), which is expected to not compile:

#include <type_traits>

template <int Z=0> std::enable_if_t<Z> function () { }

int main () {
    function();
}

The error messages that GCC 9 outputs in C++17 mode are:

g++  --std=c++17 -W -Wall -pedantic  smelly_template.cpp   -o build-c++17/smelly_template
smelly_template.cpp: In function ‘int main()’:
smelly_template.cpp:6:12: error: no matching function for call to ‘function()’
    6 |   function();
      |            ^
smelly_template.cpp:3:40: note: candidate: ‘template<int Z> std::enable_if_t<(Z != 0)> function()’
    3 | template <int Z=0> std::enable_if_t<Z> function () { }
      |                                        ^~~~~~~~
smelly_template.cpp:3:40: note:   template argument deduction/substitution failed:
In file included from smelly_template.cpp:1:
/usr/include/c++/9/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = (0 != 0); _Tp = void]’:
smelly_template.cpp:3:40:   required by substitution of ‘template<int Z> std::enable_if_t<(Z != 0)> function() [with int Z = 0]’
smelly_template.cpp:6:12:   required from here
/usr/include/c++/9/type_traits:2384:11: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
 2384 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~

My question is pretty simple: Are these the error message I should expect to see when using enable_if correctly (e.g. are they just notes about what the compiler tried to do), or did I screw something up?

The reasons I am uncertain are:

  • I was expecting the error output to only be "no matching function for call to 'function()'", and to stop there, as if I was calling a vanilla function that didn't exist. (I do not know where this expectation comes from, though.)
  • In all the posts, tutorials, and documentation I've read concerning enable_if, every time somebody encounters the "no type named 'type' in enable_if" error, it always seems to be because they're doing something incorrectly (just on a cursory search: example, example, example, the list goes on).

So I'm wondering if I'm doing something incorrectly because I am also seeing the "no type named 'type'" error.

While it's true that I'm seeing the ultimate expected behavior -- compilation failure -- I'm more concerned right now about perfectly correct usage than simply meeting the program requirement (compilation failure) in some form.

Jason C
  • 38,729
  • 14
  • 126
  • 182
  • 1
    *"Are these the error message"* Do you see one or multiple error messages? :) -- I'm asking because it's just one convoluted error.. – dyp Dec 30 '20 at 17:14
  • @dyp Well, my (mis?)interpretation of the output is two notes and two error messages; I am reading it that way because I see "note:" twice, and "error:" twice. There's the one about the lack of matching function call, then the last message about the missing `type` member. – Jason C Dec 30 '20 at 17:15
  • @dyp Is the "required from here" just before that last one the hint I missed that those are all chained together in GCC's mind? (Good question btw, thought provoking, heh) – Jason C Dec 30 '20 at 17:17
  • 1
    The structure of these messages is kinda bonkers. It "randomly" injects file locations and instantiation stacks which make it appear as if an entirely new message starts. – dyp Dec 30 '20 at 17:27
  • 1
    If you are after hard errors and compilation failing (as opposed to controlling overload resolution), `static_assert` is easier on the eyes. – StoryTeller - Unslander Monica Dec 30 '20 at 17:28
  • @StoryTeller-UnslanderMonica That's a good idea; you mean [like this](https://wandbox.org/permlink/IBibcGId3Q998oMn)? – Jason C Dec 30 '20 at 17:30
  • 1
    @JasonC - Yup. It's a saner diagnostic (not to mention the custom message telling one why the condition is important is a boon). It's slightly different to the `enable_if` approach (the compiler can't know it's an invalid call during overload resolution), SFINAE and some metaprogramming cannot be done when static asserting in the body. But if you don't need the dark arts to be applicable to your function template, I think it'll make for a better user experience with the API. – StoryTeller - Unslander Monica Dec 30 '20 at 17:34
  • Agreed; and seems to be clear with [member templates, too](https://wandbox.org/permlink/2gooVylnzbvEV7AG). There's still value in my question here, for me, just in learning about `enable_if`, but I think I'm going to switch over to the `static_assert`s when possible. Definitely like the ability to explain the problem in the message (better than my previous solution of `// hi. if you get an error on this line it's because...`). Thanks for the suggestion!! – Jason C Dec 30 '20 at 17:38
  • 1
    If you're looking for more readable diagnostics... try `clang` :P https://wandbox.org/permlink/oZtM7xqGWNaqgO2Z -- though it drastically abbreviates `enable_if`-related errors... to the point where they're not informative enough anymore :( – dyp Dec 30 '20 at 17:38

4 Answers4

3

std::enable_if_t is a template that causes substitution to fail if the condition is false. Because of SFINAE this does not cause the program to be ill-formed, when it happens during overload resolution. You are passing it a false value as the default value, so for a call to function(), without any additionally specified template parameters, overload resolution will fail.

If you change template <int Z = 0> part to int Z = 1 then I would expect the code to compile.


More to the second part of the question: Are these other errors expected?

smelly_template.cpp:3:40: note: candidate: ‘template<int Z> std::enable_if_t<(Z != 0)> function()’
    3 | template <int Z=0> std::enable_if_t<Z> function () { }
      |                                        ^~~~~~~~
smelly_template.cpp:3:40: note:   template argument deduction/substitution failed:
In file included from smelly_template.cpp:1:

Yes, the compiler tries to help you by showing what it tried, whenever overload resolution fails. Modern versions of gcc and clang will show you every overload that was available and why it couldn't be used. In this case, it is explaining why overload resolution failed with the one overload that it tried. These kinds of errors when overload resolutions fails are extremely helpful in large programs.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • *"Because of SFINAE this does not cause the program to be ill-formed."* I'd clarify that it's not _immediately ill-formed_. The template instantiation is removed from overload resolution, and overload resolution continues. It fails here because there are no other candidates. – dyp Dec 30 '20 at 17:12
  • 1
    thanks @dyp, yeah i significnatly edited it – Chris Beck Dec 30 '20 at 17:13
2

I was expecting the error output to only be "no matching function for call to 'function()'", and to stop there, as if I was calling a vanilla function that didn't exist.

Exactly what you get: "error: no matching function for call to ‘function()’"

You also get another additional "note", just in case you're expecting the disabled function() is available, that inform you that you have a substitution failure.

In all the posts, tutorials, and documentation I've read concerning enable_if, every time somebody encounters the "no type named 'type' in enable_if" error, it always seems to be because they're doing something incorrectly

Not necessarily.

Sometimes you want to have alternative versions of the same function; for example

template <int Z>
std::enable_if_t<Z==0>  function ()
 { std::cout << "version zero" << std::endl; }

template <int Z>
std::enable_if_t<Z!=0>  function ()
 { std::cout << "version non zero" << std::endl; }
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    It's worth noting that the "no type named 'type'" error is just how how the SFINAE hackery works here (all the time). The folks who got their error when doing something wrong had a problem of expectations most like. – StoryTeller - Unslander Monica Dec 30 '20 at 17:21
2

At some point in the past, I've written a parser for GCC error output. Nowadays, I'd recommend --fdiagnostics-format=json, but it has/had also drawbacks. Anyway, here's a deconstruction of the error message:

# First, the location of the following message
smelly_template.cpp: In function ‘int main()’:

# Then, the main message
smelly_template.cpp:6:12: error: no matching function for call to ‘function()’
# followed by the code location where it happened
    6 |   function();
      |            ^

# Now, we get "child diagnostics". They still belong to the same error.
# The child diagnostics explain why the parent diagnostic happened
# First child diagnostic: we have an overload resolution candidate, but it's not viable
smelly_template.cpp:3:40: note: candidate: ‘template<int Z> std::enable_if_t<(Z != 0)> function()’
    3 | template <int Z=0> std::enable_if_t<Z> function () { }
      |                                        ^~~~~~~~

# Child of the first child diagnostic. You can see this by the number of spaces after `note: ` :)
# The overload candidate was removed from the overload set because of substitution failure
smelly_template.cpp:3:40: note:   template argument deduction/substitution failed:

# Now we get a template instantiation stack for the grandchild diagnostic
# Starting with a file
In file included from smelly_template.cpp:1:
# Backwards from deepest template instantiation
/usr/include/c++/9/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = (0 != 0); _Tp = void]’:

# Next instantiation level
smelly_template.cpp:3:40:   required by substitution of ‘template<int Z> std::enable_if_t<(Z != 0)> function() [with int Z = 0]’

# Next instantiation level
smelly_template.cpp:6:12:   required from here

# Why did the substitution fail? (reason for the grandchild diagnostic)
/usr/include/c++/9/type_traits:2384:11: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
 2384 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~

See also the output of -fdiagnostics-format=json. This is imperfect because it only uses one level of nesting, despite the logical error structure having more levels.

[
  {
    "kind": "error",
    "children": [
      {
        "kind": "note",
        "locations": [
          {
            "finish": {
              "line": 3,
              "file": "prog.cc",
              "column": 47
            },
            "caret": {
              "line": 3,
              "file": "prog.cc",
              "column": 40
            }
          }
        ],
        "message": "candidate: 'template<int Z> std::enable_if_t<(Z != 0)> function()'"
      },
      {
        "kind": "note",
        "locations": [
          {
            "finish": {
              "line": 3,
              "file": "prog.cc",
              "column": 47
            },
            "caret": {
              "line": 3,
              "file": "prog.cc",
              "column": 40
            }
          }
        ],
        "message": "  template argument deduction/substitution failed:"
      },
      {
        "kind": "error",
        "locations": [
          {
            "finish": {
              "line": 2384,
              "file": "/opt/wandbox/gcc-9.3.0/include/c++/9.3.0/type_traits",
              "column": 21
            },
            "caret": {
              "line": 2384,
              "file": "/opt/wandbox/gcc-9.3.0/include/c++/9.3.0/type_traits",
              "column": 11
            }
          }
        ],
        "message": "no type named 'type' in 'struct std::enable_if<false, void>'"
      }
    ],
    "locations": [
      {
        "caret": {
          "line": 6,
          "file": "prog.cc",
          "column": 15
        }
      }
    ],
    "message": "no matching function for call to 'function()'"
  }
]
dyp
  • 38,334
  • 13
  • 112
  • 177
  • 1
    Oh wow; this is incredibly helpful and I learned something new and extremely useful too (the `-fdiagnostics-format` option). Thank you *so much* for breaking this down. – Jason C Dec 30 '20 at 17:26
  • I feel like GCC could use a level of indentation or something in sub-errors in big chains like that. Also this brings back memories of this gcc output parser that cleared up template messages, I'd used it years ago and forgot about it, I can't remember what it was though... – Jason C Dec 30 '20 at 17:27
  • 1
    IIRC it has some "indentation" in the form of spaces within the line (after some `:`). But yeah, the output is really not great. The JSON form is much better to process, so I _should_ recommend just writing a pretty printer for it - however, last time I've checked (gcc 9), the output was not as detailed as the supposedly human-readable default output :( – dyp Dec 30 '20 at 17:33
  • 1
    Ah yes, there are no instantiation stacks (which I find important), and there's a lack of nesting (grandchild diagnostic -> child/sibling diagnostic). – dyp Dec 30 '20 at 17:34
  • 1
    Hah, my parser detects that the last "error" is actually a child of the `note: template argument deduction/substitution failed:` by finding the `:` at the end of the `note: ...:` message. If such a message ends with `:`, there will be a follow-up explanation (`error:` line). – dyp Dec 30 '20 at 17:36
2

You have to understand that SFINAE was not and is not a language design with the purpose of disabling/enabling templates. It's a side-effect of the way that the template system and language works. People figured out Oh we can use templates this way to disable/enable template specializations. That's why the errors you get when using SFINAE may not be the most straight forward. Some compilers I've seen they treat std::enable_if specifically and get better error messages like "template specialization disabled by enable_if because..."

A language feature that is designed mainly for this purpose is C++20's concepts.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • This is a good note; I wasn't really thinking about it that way. Makes a lot of stuff make a bit more sense now. Thanks. – Jason C Dec 30 '20 at 17:47