Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

SFINAE question

49 views
Skip to first unread message

Daniel

unread,
Apr 6, 2019, 8:00:45 PM4/6/19
to
Consider the function

template <class J>
J f()
{
return J();
}

I'd like to have two implementations of f. One when J is an instantiation of the template class

template <class T>
struct A
{
typedef T t_type;
typedef typename T::char_type char_type; // (*)
};

And a second when J is any other class that has a default constructor.

I tried the following:

template <class T,class Enable=void>
struct is_A : std::false_type
{};

template <class T>
struct is_A<T, decltype(std::declval<A<typename T::t_type>>(),void())> : std::true_type
{};

template <class J>
typename std::enable_if<is_A<J>::value,J>::type
f()
{
std::cout << "J is a specialization of A\n";
return J();
}

template <class J>
typename std::enable_if<!is_A<J>::value, J>::type
f()
{
std::cout << "J is not a specialization of A\n";
return J();
}

But when attempting to construct

struct B
{
typedef char char_type;
};

auto j1 = f<A<B>>(); // (1)
auto j2 = f<std::vector<int>>(); // (2)

(2) fails to compile because of line (*) in the definition of A. So this
approach doesn't work.

Any suggestions?

Thanks,
Daniel

Alf P. Steinbach

unread,
Apr 6, 2019, 11:44:34 PM4/6/19
to
Your code, with necessary #include statements added as shown below,
works with MingW g++ 8.2.0 and Visual C++ 2019,

Try to create a small but complete example that reproduces the undesired
behavior.

When/if you ask here about it, if it still doesn't work, consider ALL
the points in the FAQ item "How do I post a question about code that
doesn't work correctly?", available at e.g. <url:
http://www.dietmar-kuehl.de/mirror/c++-faq/how-to-post.html#faq-5.8>.


-------------------------------------------------------------------------
#include <type_traits> // std::false_type

// template <class J>
// J f()
// {
// return J();
// }

template <class T>
struct A
{
typedef T t_type;
typedef typename T::char_type char_type; // (*)
};

template <class T,class Enable=void>
struct is_A : std::false_type
{};

template <class T>
struct is_A<T, decltype(std::declval<A<typename T::t_type>>(),void())> :
std::true_type
{};


#include <iostream>
#include <vector>

template <class J>
typename std::enable_if<is_A<J>::value,J>::type
f()
{
std::cout << "J is a specialization of A\n";
return J();
}

template <class J>
typename std::enable_if<!is_A<J>::value, J>::type
f()
{
std::cout << "J is not a specialization of A\n";
return J();
}

struct B
{
typedef char char_type;
};

auto main() -> int
{
auto j1 = f<A<B>>(); // (1)
auto j2 = f<std::vector<int>>(); // (2)
(void)j1, (void)j2;
}
-------------------------------------------------------------------------


Results:


J is a specialization of A
J is not a specialization of A


The code can IMO be expressed more cleanly, but that's a different matter.


Cheers!,

- Alf

Sam

unread,
Apr 7, 2019, 8:41:50 AM4/7/19
to
Daniel writes:

> Consider the function
>
> template <class J>
> J f()
> {
> return J();
> }
>
> I'd like to have two implementations of f. One when J is an instantiation of
> the template class
>
> template <class T>
> struct A
> {
> typedef T t_type;
> typedef typename T::char_type char_type; // (*)
> };
>
> And a second when J is any other class that has a default constructor.

It's much simpler to avoid SFINAE with an inline wrapper that uses partial
specialization of a template type. I expect any modern C++ compiler to
optimize away the wrapper call to nothing.

#include <iostream>

template <class T>
struct A
{
typedef T t_type;
};

template <class J>
struct generic_f {

static J f()
{
std::cout << "Generic" << std::endl;
return J();
}
};

template<typename J>
struct generic_f<A<J>> {

static A<J> f()
{
std::cout << "Specialized" << std::endl;
return A<J>();
}
};

template <class T>
inline T f()
{

return generic_f<T>::f();
}

int main()
{
f<int>();
f<A<int>>();
return 0;
}

Daniel

unread,
Apr 7, 2019, 10:49:00 AM4/7/19
to
On Saturday, April 6, 2019 at 11:44:34 PM UTC-4, Alf P. Steinbach wrote:
>
> Your code, with necessary #include statements added as shown below,
> works with MingW g++ 8.2.0 and Visual C++ 2019,

But not with Visual C++ 140 (2015) or 141 (vs 2017). Given that, I had
assumed something was wrong with my understanding of SFINAE with decltype
and declval, but I checked and I see that it does work with GCC 4.8 and
later on linux, and clang 3.9 and later on linux, so I guess this is an
earlier Visual C++ bug. But nonetheless, a showstopper, the code having to
work in all these environments.

I don't suppose you would have any suggested alternatives that would work
with Visual C++ 140 (2015) or 141 (vs 2017)?

Thanks,
Daniel







Daniel

unread,
Apr 7, 2019, 10:54:46 AM4/7/19
to
On Sunday, April 7, 2019 at 8:41:50 AM UTC-4, Sam wrote:
>
> It's much simpler to avoid SFINAE with an inline wrapper that uses partial
> specialization of a template type. I expect any modern C++ compiler to
> optimize away the wrapper call to nothing.
>
Thanks, I may end up doing it that way.

Daniel

Daniel

unread,
Apr 7, 2019, 3:33:11 PM4/7/19
to
On Sunday, April 7, 2019 at 10:49:00 AM UTC-4, Daniel wrote:
> On Saturday, April 6, 2019 at 11:44:34 PM UTC-4, Alf P. Steinbach wrote:
> >
> > Your code, with necessary #include statements added as shown below,
> > works with MingW g++ 8.2.0 and Visual C++ 2019,
>
> But not with Visual C++ 140 (2015) or 141 (vs 2017).

I'm currently experimenting with code similar to that below. Any additional
comments appreciated.

Thanks,
Daniel

#include <type_traits>
#include <iostream>
#include <vector>

template <class T>
struct A
{
typedef T t_type;
typedef typename T::char_type char_type; // (*)
};

template <class T,class Enable=void>
struct is_A : std::false_type
{};

#if defined(_MSC_VER) && _MSC_VER < 1916
template <class T>
struct is_A<T, typename std::enable_if<!std::is_void<typename T::t_type>::value && !std::is_void<typename T::char_type>::value>::type> :
std::true_type
{};
#else
template <class T>
struct is_A<T, decltype(std::declval<A<typename T::t_type>>(),void())> :
std::true_type
{};
#endif

template <class J>
typename std::enable_if<is_A<J>::value,J>::type
f()
{
std::cout << "J is a specialization of A\n";
return J();
}

template <class J>
typename std::enable_if<!is_A<J>::value, J>::type
f()
{
std::cout << "J is not a specialization of A\n";
return J();
}

struct B
{
typedef char char_type;
};

int main()
{

Alf P. Steinbach

unread,
Apr 8, 2019, 4:13:59 AM4/8/19
to
I sort of wish now that I had not hastily uninstalled VS 2017.

I think I still have VS 2012...

But, try rewriting with specialization instead of SFINAE, maybe?


Cheers!,

- Alf


Alf P. Steinbach

unread,
Apr 8, 2019, 4:27:45 AM4/8/19
to
On 07.04.2019 16:48, Daniel wrote:
Wait, with VS 2017, did you remember to specify the version of the standard?

Like, `/std:c++17`?

This is a useful set of options for that compiler:

/nologo /std:c++17 /Zc:__cplusplus /utf-8 /EHsc /GR /W4 /FI"iso646.h" /D
_CRT_SECURE_NO_WARNINGS /D _STL_SECURE_NO_WARNINGS


Cheers & hth.,

- ALf

Daniel

unread,
Apr 8, 2019, 8:15:57 AM4/8/19
to
On Monday, April 8, 2019 at 4:13:59 AM UTC-4, Alf P. Steinbach wrote:
>
> I sort of wish now that I had not hastily uninstalled VS 2017.
>
> I think I still have VS 2012...
>
>
It appears there were a number of SFINAE issues with VS 2017, it looks like
this one was fixed in version 15.9 (or _MSC_VER 1916.) I have version 15.8.7
installed, and AppVeyor has 15.9, I experienced the bug on my desktop,
AppVeyor passed. As noted in my other post, it also passed on gcc 4.8 and
later, and clang 3.9 and later.

Since I don't want to give up support for vs2015 over this, I've #def'ed a
hack for _MSC_VER < 1916.

Your reply was helpful.

Daniel



0 new messages