2

I have a template struct SFoo that contains a member struct SZug:

template <typename tTYPE>
struct SFoo
  {
    struct SZug {};
  };

I have another struct SBar that takes a type parameter:

template <typename tTYPE>
struct SBar
  { /* stuff */ };

I would like to specialize SBar using SZug for the type parameter, like so:

template <typename tTYPE>
struct SBar<typename SFoo<tTYPE>::SZug>
  { /* different stuff */ };

This doesn't compile - LLVM outputs:

non-deducible template parameter 'tTYPE'

While a compiler could easily deduce this if it wished, I'm guessing it's just that the C++ spec would need to specifically cover this case.

Is there any way to achieve this?
(note: I'm currently working around it by moving SZug outside of SFoo and using a using declaration, but it's ugly.)

xaxazak
  • 748
  • 4
  • 16
  • What do you intend to do after? It is not clear to me what you expect the "specialization" to do. Can you show us how you would declare a variable of the non-specialized and specialized `SBar`? – Holt Jul 29 '16 at 06:30
  • I'm actually using these purely as trait types, so they're never actually instantiated. Bar simply provides a constexpr member pointer for another (not mentioned) class (plus a few other things). Usually the member pointer needs to be explicitly specified, but for the `SZug` specialization it can be determined. -- (Ultimately this is all to do with generating N-to-N generic embedded containers from 1-to-N generic containers - the member pointer points to the container or node info "embedded" (i.e. a member variable) in the user's type). – xaxazak Jul 29 '16 at 08:21
  • I you can slightly modified `SZug` to detect it, it is pretty easy to do what you want using SFINAE (see my answer). If you cannot, I am not sure there is a way to detect that `tTYPE` is the `SFoo::SZug` class. – Holt Jul 29 '16 at 08:23

2 Answers2

2

I am not sure I fully understood what you want to do, but you could try the following (it only requires adding a specific attributes to SZug:

template <typename tTYPE>
struct SFoo {
    struct SZug {
        // Add this to be able to obtain SFoo<T> from SFoo<T>::SZug
        using type = tTYPE;
    };
};

Then a small template to check if a type is a SFoo<T>::SZug:

template <typename tTYPE, typename Enabler = void>
struct is_SZug: public std::false_type { };

template <typename tTYPE>
struct is_SZug<tTYPE, typename std::enable_if<
  std::is_same<tTYPE, typename SFoo<typename tTYPE::type>::SZug>{}
>::type>: public std::true_type { };

And a slight modification to the SBar template to enable the "specialization" if the type is a SZug:

template <typename tTYPE, typename Enabler = void>
struct SBar
  { static void g(); };

template <typename tTYPE>
struct SBar<tTYPE, typename std::enable_if<is_SZug<tTYPE>{}>::type>
  { static void f(); };

A little check:

void f () {
  SBar<int>::g();
  SBar<SFoo<int>::SZug>::f();
}

Note: You could also directly set SFoo<T> as the type attribute in SFoo<T>::SZug, you would simply need to change the second argument of std::is_same a little.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Holt
  • 36,600
  • 7
  • 92
  • 139
  • Yep, this does solve the problem, tyvm. For my current problem, adding the `using type = tTYPE;` is pretty simple. The extra parameter on `SBar` is more annoying, but still do-able. I'm currently deciding whether this is tidier than my current fix. - If no better answer arrives by tomorrow I'll tick this as solved. Cheers. – xaxazak Jul 29 '16 at 08:32
  • @xaxazak It is a commonly used idiom to add such `Enabler` parameter to templates (it should be automatically deduced most of the time), but it may be an issue if you need to use `SBar` as a template template argument. Maybe if you could elaborate on why it is annoying and I might be able to propose a more appropriate solution? – Holt Jul 29 '16 at 08:35
  • Mostly it's modularity. SBar is already used frequently (in 1-to-N containers), and shouldn't need to know about this new feature (N-to-N containers). But it's all my own code so I can update it, and it's not used in template-template stuff anywhere (although `SZug` uses a template-template parameter). – xaxazak Jul 29 '16 at 08:45
  • @xaxazak You only need to update the "main" declaration of `SBar`, you can even simply add a forward declaration prior to anything. Maybe I am missing something but it should not impact anything since the `Enabler` parameter is defaulted. – Holt Jul 29 '16 at 08:50
  • Ah yes, true. However, after implementing a few cases (eg: heap-allocated vs custom-allocated vs user-managed nodes) I found that moving `SZug` outside `SFoo` ultimately ends up being tidier because it only needs to be done once and it's 100% modular. However, it's good to know what options are there, and perhaps the question might help someone else. - I do wonder if C++ would benefit or suffer if the syntax in the question body was made legal. – xaxazak Jul 30 '16 at 08:05
  • Very good answer. Personally, I would have moved some of the auxiliary stuff to a detail namespace. – Ami Tavory Jul 30 '16 at 08:14
1

You can get the effect for which you're looking through the following (which prints out 0 1, BTW):

#include <type_traits>
#include <iostream>

namespace detail
{   
    struct SZugBase{};
}   

template <typename tTYPE>
struct SFoo                                                                                                                                
{   
    struct SZug : public detail::SZugBase {}; 
};  

template<typename tType, bool IsFoo>
struct SBarBase
{   
    int value = 0;
};  

template<typename tType>
struct SBarBase<tType, true>
{   
    int value = 1;
};  

template <typename tTYPE>
struct SBar : public SBarBase<tTYPE, std::is_convertible<tTYPE, detail::SZugBase>::value>
{ /* stuff */ };

int main()
{   
    SBar<int> b0; 
    SBar<SFoo<int>::SZug> b1; 

    std::cout << b0.value << " " << b1.value << std::endl;
}   

Explanation

First, we give SZug a regular-class base:

namespace detail
{   
    struct SZugBase{};
}   

template <typename tTYPE>
struct SFoo                                               
{   
    struct SZug : public detail::SZugBase {}; 
};  

Note the following:

  1. SZugBase is not parameterized by anything, so it is easy to refer to it independently of the parameter of SFoo

  2. SZugBase is in a detail namespace, so, by common C++ conventions, you're telling clients of your code to ignore it.

Now we give SBar two base classes, specialized on whether something is convertible to the non-template base of SZug:

template<typename tType, bool IsFoo>
struct SBarBase
{   
    int value = 0;
};  

template<typename tType>
struct SBarBase<tType, true>
{   
    int value = 1;
};  

Finally, we just need to make SBar a subclass of these bases (depending on the specialization):

template <typename tTYPE>
struct SBar : public SBarBase<tTYPE, std::is_convertible<tTYPE, detail::SZugBase>::value>
{ /* stuff */ };

Note that you don't specialize SBar here, you rather specialize the base classes. This effectively gives the same effect, though.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • That works for `SBar> b1;`. Unfortunately, I'm looking for `SBar::` **`SZug`** `> b1;` (which AFAICT would still print 0). – xaxazak Jul 29 '16 at 08:37
  • @xaxazak You're right - I missed that detail in the question, but the principle applies. A few small changes in the details of the answer made this apply to your original intent. The main points are: 1. A non-template "detail" base, and 2. Specializing a base class based on whether it is convertible to this base. – Ami Tavory Jul 29 '16 at 12:54
  • Yep, that does fix the issue. I was crossing my fingers there might be a tidier way to do it. Both your solutions and Holt's could be accepted, but IMHO I think adding a fixer member type (Holt's method) is very slightly nicer than inheriting from a fixer struct. – xaxazak Jul 30 '16 at 08:09
  • Sure, you should accept whatever answer you think is the best (I'll read his answer too - always good to learn some new ideas). – Ami Tavory Jul 30 '16 at 08:11