4

I have some constants named like this:

template<int n> class usart {
private:
    usart();
public:
    enum class tx {};
    enum class rx {};
    enum class ck {};
};

template<> class usart<1> {
public:
    enum class tx { A9  = gpio::A9,  C4 = gpio::C4 };
    enum class rx { A10 = gpio::A10, C5 = gpio::C5 };
    enum class ck { A8  = gpio::A8 };
};

// two more of these

where gpio is just a simple integer enum.

I'd like to enforce some type safety on my class in another file:

class USART {
public:
    template<int N>
    USART(typename usart<N>::tx pin_tx, typename usart<N>::rx pin_rx) {
        //This signature enforces correct pins with types, doesn't it?
    }
};

However, when I use this with

USART us = USART(usart<1>::tx::A9, usart<1>::rx::A10);

I get the error

error: expected ')' before 'pin_tx'

Why is this syntax illegal? EDIT: typename

This now gives me this error when I try and instantiate the class:

error: no matching function for call to 'USART::USART(usart<1>::tx, usart<1>::rx)'
note: template<int N> USART::USART(typename usart<N>::tx, typename usart<N>::rx)
note:   template argument deduction/substitution failed:
note:   couldn't deduce template parameter 'N'
Eric
  • 95,302
  • 53
  • 242
  • 374
  • Yep, adding `typenmae` fixes that error, but now I get `no matching function for call to 'USART::USART(usart<1>::tx, usart<1>::rx)'` – Eric Jan 28 '13 at 09:54
  • @sbi: I'm not immediately seeing the connection between that and my question – Eric Jan 28 '13 at 09:56
  • Um. Since nobody answered yet, why don't you change your question accordingly? Please indicate where exactly which exact error message occures. I am sorry for the close vote now. I'd take it back if I could. – sbi Jan 28 '13 at 09:56
  • @sbi: Updated the question. I'd say the place where the error occurs is obvious, since there's only one call to `USART::USART` – Eric Jan 28 '13 at 10:00
  • 2
    This is non-deduced context. Basically, you cannot expect to retrieve `X` from `Foo::Y`. – n. m. could be an AI Jan 28 '13 at 10:08
  • I cannot think of a way to work around this that does not involve emulating enums by hand :( – R. Martinho Fernandes Jan 28 '13 at 10:14
  • 1
    @R.MartinhoFernandes: maybe `usart` could contain a friend free function that takes `usart::tx` and returns type `usart`. Then if `USART` does `decltype(that_function(pin_tx))`, it gets a type from which `N` can be deduced. And if someone does the thing in your answer then either `1` is deduced (if they don't also define the friend) or the call is ambiguous (if they do), which diagnoses their error. I'm not quite sure off hand how to put all that together, though, it might require an extra level of indirection for USART. – Steve Jessop Jan 28 '13 at 10:19
  • Actually I think the call isn't ambiguous, its an illegal redefinition of the friend function. Hopefully they get an error, though. – Steve Jessop Jan 28 '13 at 10:24
  • @SteveJessop: Any chance you could try to implement that? – Eric Jan 28 '13 at 10:28
  • @SteveJessop Hmm, yeah, that might work. I'm too busy to give it a try right now, though, so maybe someone else *wink* *wink* can try it and post as an answer. – R. Martinho Fernandes Jan 28 '13 at 10:30

1 Answers1

15

The template parameters used in the function arguments are not deducible because the the arguments are of dependent types.

"But that's silly!" you would say; "It is obvious that N is 1! Why can't the compiler have the smarts to deduce that?"

Consider the following:

template<> class usart<4321> {
public:
    typedef usart<1>::tx tx;
    typedef usart<1>::rx rx;
    typedef usart<1>::ck ck;
};

Should N be 1 or 4321? After all, usart<4321>::tx and usart<1>::tx are the same type.

The compiler cannot know what N should be without checking that only one instantiation of usart has that exact type as the tx member. This would either require too many instantiations or way too complex logic to prove that no instantiation would result in that in the general case. Sure, it might be simple to implement something for this particular case, but that isn't terribly useful for all other cases. The C++ committee simply decided to not require this of compiler writers.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • That makes a lot of sense. I think I'll resort to overloading for `usart1::tx`, `usart2:tx` etc, since I don't really gain anything from the template parameters here. – Eric Jan 28 '13 at 16:34