8

Expanded version here.

We can create objects of class templates that have default template parameters without typing angle brackets:

int main()
{
    std::less a;
}

But we can't do that for member variables:

struct S
{
    std::less a; // I want only type std::less<void> here
};

It looks like the first case works due to CTAD but why can't compiler deduce std::less<void> in the second case? Maybe we shouldn't apply CTAD there but provide different mechanism.

Is this considered a bug in the standard? Is there a proposal to fix it?

My use case:

I have a class template which provides default argument, like this:

template <typename T = int>
class Foo {};

The template parameter is an expert-only feature that I myself never use but it is there for those 1% of experts who want that total flexibility. Now for other 99% I want to hide the fact that Foo is actually a class template but it doesn't work because users have to type Foo<> when declaring it as a member variable, current solution is this:

template <typename T = int>
class BasicFoo {};

using Foo = BasicFoo<>;

But it complicates implementation code and is not elegant at all.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • 1
    No moreso than the fact that you can't use `auto` in a member variable. – Nicol Bolas Mar 06 '19 at 15:45
  • Just that the syntax is the same does not mean it's the same thing. The first is a variable declaration, the other is a field declaration. – Max Langhof Mar 06 '19 at 15:46
  • 1
    For those wondering what CTAD means : [Class template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction). – François Andrieux Mar 06 '19 at 15:51
  • 1
    @Lyberta: "*Now for other 99% I want to hide the fact that Foo is actually a class template*" Well, you can't; C++ does not let you. Even if CTAD worked here, it wouldn't work in general. You couldn't have a function that took `Foo` as a parameter. You can't talk to static members of `Foo` or get member pointers from it. CTAD is not there to *hide* the fact that something is a template. It's a convenience feature to allow the compiler to figure out what it already knows. – Nicol Bolas Mar 06 '19 at 16:24
  • @NicolBolas Can you expand it into the answer with proper code samples? I wanna see where exactly the syntax roadblock lies. –  Mar 06 '19 at 16:30
  • 2
    @Lyberta: Your question is about CTAD in member variables. I was responding to a side issue: the fact that the reason you *want* CTAD in member variables is never going to work out, even if you could get CTAD in member variables. So such an "answer" would not answer the question you have asked. And since everyone else already answered based on the issue of CTAD in member variables, it's not fair for you to change the question. – Nicol Bolas Mar 06 '19 at 16:34
  • How does adding the single line `using Foo = BasicFoo<>;` complicate implementation code? Anyway, if you want to describe your _real_ problem in greater detail, please do so in a new question. – Max Langhof Mar 06 '19 at 16:49
  • @MaxLanghof Because now I have to write `BasicFoo` in all the implementation code. –  Mar 06 '19 at 16:52
  • 1
    @Lyberta: You don't get to radically alter your question after people have answered it. – Nicol Bolas Mar 06 '19 at 16:53

1 Answers1

11

No, it is not a bug. It is because there could be different constructors called for the same member variable (called through class' constructor init list), potentially yielding different deduction result.

To prevent the potential for such conflict, you have to provide template arguments to non-static members. (Static members are not a problem, because there will a be a single constructor call for them)

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • There is no in-place initializer so in my mind the type is fixed even before calling the constructor. –  Mar 06 '19 at 15:48
  • @Lyberta: That's fine... in your mind. But that kind of thinking is not something the standard wants to promote. – Nicol Bolas Mar 06 '19 at 15:49
  • @Lyberta CTAD relies on the constructor call signature, so it makes perfect sense if it doesn't work without a constructor call. – Max Langhof Mar 06 '19 at 15:49
  • @MaxLanghof: It also works in aggregate initialization, if you have an appropriate deduction guide. – Nicol Bolas Mar 06 '19 at 15:49
  • 1
    @Lyberta how do you mean? As I tried to explain, there could be different constructor calls for the same member variable. How do you want CTAD rules to work? – SergeyA Mar 06 '19 at 15:50
  • @SergeyA I guess I don't want CTAD there, updated the question. –  Mar 06 '19 at 15:51
  • 1
    I do not understand what you are asking now. Without CTAD, `std::less` is a template, so it has to be used as such. `std::less<> a;` will work fantastically well. – SergeyA Mar 06 '19 at 15:55
  • @SergeyA But it exposes its templateness which is not always desirable. –  Mar 06 '19 at 16:04
  • @Lyberta `using default_less = std::less<>` will hide it's blatant templateness from all but the most skilled private detectives :) – SergeyA Mar 06 '19 at 16:17
  • @SergeyA But why do we have to have this workaround? I don't see any counterarguments why my proposal shouldn't work. –  Mar 06 '19 at 16:25
  • 3
    @Lyberta: "*But it exposes its templateness which is not always desirable.*" It is a template; it is a fact that *will be exposed* to the user. CTAD is not about hiding the fact that something is a template; it's a convenience feature to keep people from having to retype the same information. – Nicol Bolas Mar 06 '19 at 16:29
  • 3
    @Lyberta because it is not a workaround. `std::less` will always be a template. CTAD is (essentially) a typing aid, which allows people to not repeat the same information twice - for the constructor call and template argument. I also do not like how your questions evolves over time, and now it became quite a different question, – SergeyA Mar 06 '19 at 16:35
  • @Lyberta There is nothing preventing you from designing a language where your proposal would "work". But to make it work _in C++_, you would have to change the language and grammar significantly. And doing all that in order to _obfuscate_ code is not going to happen. FWIW, `std::string` (which [is actually](https://en.cppreference.com/w/cpp/string/basic_string) a `std::basic_string`) has and solves exactly your problem. – Max Langhof Mar 06 '19 at 16:47
  • @MaxLanghof There was a problem with terse concept syntax because it obfuscated function templates. But omitting angle bracket in my proposed cased doesn't change the semantics i.e. no angle brackets mean a type and not a template in cases where type is expected. –  Mar 06 '19 at 16:50
  • @Lyberta this now becomes a rather moot discussion. If you think you know how to significantly improve C++ usability as a language, while preventing backward compatibility and not placing unreasonable burden on compiler writers (and are also ready to submit some prototype compiler code which would support it!) by all means, submit a proposal and we will all (as a community) benefit from it! – SergeyA Mar 06 '19 at 16:52