Variadic template issue - c++

I have the following code I'm trying to adapt for my own purposes. This is a learning exercise for me to attempt an update of my C++ skills.
This was originally written with Clang 3.1 in mind as far as I can tell.
I've tried compiling with Clang versions from 3.1 to 4.0 and GCC 4.9 to 7.1 with very similar results.
These are the error messages from GCC 5.1
Error 1: In instantiation of 'constexpr Struct<Fields>::Struct(T&& ...) [with T = {Struct<foo<int>, bar<double> >&}; Fields = {foo<int>, bar<double>}]':
<source>:46:12: required from here
Error 2: <source>:28:61: error: mismatched argument pack lengths while expanding 'Fields'
constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {}
Please ELI5 if you have the patience :P
You can see this in godbolts compiler doohickey here:
https://godbolt.org/g/2dRPXf
EDIT:
Given the answers by #Passer-By and #Daniel-Jour, I wonder if Struct(Struct const &) = default; is even necessary. Will removing this constructor from Struct have effects of which I am not aware or do no foresee (I am no C++ Swami!)?
Is the constructor constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {} a good stand-in for what would otherwise be generated by Struct(Struct const &) = default;?
I'm feeling pretty ambiguous about either proposed solution so far.
End EDIT
// From http://duriansoftware.com/joe/Self-aware-struct-like-types-in-C++11.html
// edited a bit to stop the compiler whining about comments in multiline macros
#include <type_traits>
#include <utility>
#define SELFAWARE_IDENTIFIER(NAME) \
template<typename T> \
struct NAME { \
T NAME; /* field name */ \
constexpr static char const *name() { return #NAME; } /* field type */ \
using type = T; /* field value generic accessor */ \
T &value() & { return this->NAME; } \
T const &value() const & { return this->NAME; } \
T &&value() && { return this->NAME; } \
};
template<typename...Fields>
struct Struct : Fields... {
// A convenience alias for subclasses
using struct_type = Struct;
// Preserve default constructors
Struct() = default;
Struct(Struct const &) = default;
// Forwarding elementwise constructor
template<typename...T>
constexpr Struct(T &&...x) : Fields{static_cast<T&&>(x)}... {} // Error 2 here
};
SELFAWARE_IDENTIFIER(foo)
SELFAWARE_IDENTIFIER(bar)
// Aliasing a Struct instance
using FooBar = Struct<foo<int>, bar<double> >;
// Inheriting a Struct instance (requires inheriting constructors)
struct FooBar2 : Struct<foo<int>, bar<double>> { using struct_type::struct_type; };
static_assert(std::is_trivial<FooBar>::value, "should be trivial");
static_assert(FooBar{2, 3.0}.foo + FooBar{2, 4.0}.foo == 4, "2 + 2 == 4");
FooBar frob(int x) {
FooBar f = {x, 0.0};
f.foo += 1;
f.bar += 1.0;
return f; // Error 1 here
}

You've fallen victim of what I know as "too perfect forwarding".
To debug this, first look closely at the error message:
instantiation of constexpr Struct<Fields>::Struct(T&& ...) [with T = {Struct<foo<int>, bar<double> >&}; Fields = {foo<int>, bar<double>}]:
This tells us that this line of code
return f;
does not as expected call the copy or move constructor, but rather the perfect forwarding constructor.
Looking at what this constructor does, it's clear that this constructor is not capable of copying or moving from a Struct. Its intended​ use case is to construct each of the fields from one of the arguments. But the error message shows that there is only a single argument of type Struct<foo<int>, bar<double> >&. Because the expansion of the arguments also expands Fields (of which there are two) you get that second error:
[..] error: mismatched argument pack lengths [..]
But why does it take the perfect forwarding constructor instead of the also available copy constructor? Because the forwarding constructor is able to provide a better candidate (an exact match actually) than the copy constructor (whose signature is Struct(Struct const &)): Struct(Struct & &&), which according to the reference combination rules is Struct(Struct &). And that's exactly what's needed to use f in return f;: after all f is a non const lvalue.
One possibility to fix this is to provide another (copy) constructor with the exact signature:
Struct(Struct & s)
: Struct(static_cast<Struct const &>(s))
{}
But if you also add in volatile you need to write a total of six constructors to have all cases covered. Not nice.
The better solution is to exclude the perfect forwarding constructor from overload resolution with SFINAE:
template<typename T>
using decay_t = typename decay<T>::type;
template<
typename...T,
std::enable_if<
(sizeof...(T) == sizeof...(Fields))
&& not_self_helper<Struct, std::tuple<decay_t<T>...>>::value
>::type * = nullptr
>
constexpr Struct(T &&...x)
: Fields{static_cast<T&&>(x)}... {}
not_self_helper checks whether a single parameter passed to a structure with a single field is of the structures own type:
template<typename, typename>
struct not_self_helper : std::true_type {};
template<typename Self>
struct not_self_helper<Self, std::tuple<Self>> : std::false_type {};
This fixes your main issue: The forwarding constructor is semantically just wrong. It does not take an arbitrary number of parameters, but needs exactly the same number of parameters as the structure has fields. Further, none of the fields should be constructed from the containing structure itself (recursive membership means infinite structure size, after all). I shortened that test to only check when there's a single argument, though. Strictly speaking, this is semantically wrong, but in practice it covers the most "dangerous" case: When your forwarding constructor is wrongfully selected for copy/move construction.

The problem consists of two parts
Overload resolution
For a variadic constructor with forwarding references
template<typename... Args>
Struct(Args&&...);
Given any lvalue arguments, Args will be deduced as lvalue references, and rvalue arguments rvalue references.
For another constructor to be called, the variadic constrcutor must not have a better conversion sequence than the constructor.
Your code includes only one other constructor that takes one parameter, the copy constructor
Struct(const Struct&);
A lvalue const Struct will bind with the copy constructor's const Struct& while a lvalue or rvalue Struct will bind with the variadic constructor's Struct& or Struct&& respectively.
Value category of returned type
A glvalue expression referring to an automatic duration variable declared in the function in a return statement is considered a xvalue, and would therefore bind to Struct&& first. When that fails, the expression will be considered a lvalue and proceed to bind to Struct& or const Struct&.
In this case, Struct&& succeeds with the variadic constructor, but results in an error.
If a move constructor is provided, the move constructor will be selected after overload resolution discards the variadic constructor.
Responding to edit
Removing your user-provided copy constructor will allow for an implicitly declared move constructor, which will make the code compile, following the reasons above.
The variadic constructor is not a good stand in for either constructors, aside from being semantically wrong, it takes arbitrary arguments but requires a fixed (in this case 2) amount of arguments to initialize the fields. If it were to be used as a copy constructor, you will get the same compile error: mismatched argument pack lengths while expanding 'Fields'
As was mentioned in Daniel Jour's answer, you should probably put some SFINAE in the variadic constructor to alleviate some pain.

Related

Passing object of struct template as a function parameter

I'm trying to create a function which takes an object of my struct template as an argument.
template <unsigned dim>
struct vec{
float d[dim];
template<typename ...T>
vec(T&&... args) : d{args...}{}
float operator[] (unsigned n) const { return d[n]; }
// ...
};
This code works fine on its own, but it starts to hassle when I want to create a function which takes an "vec" object as a parameter.
void asdf(vec<3> a){ ... }
When I create an instance of my struct as the parameter input it works fine:
asdf(vec<3>{5.f, 10.f, 3.f}); // Works fine
But when I try something like this my compiler won't buy it:
vec<3> test{5.f, 10.f, 3.f};
asdf(test); // error: cannot convert 'vec<3>' to 'float' in initialization
My IDE says the problem is in the constructor. Any help would be greatly appreciated!
You need to exclude the constructor template from overload set when being passed a vec. Otherwise it's an exact match and preferred over the copy constructor which takes a const vec& (then requires to add constness to be called).
E.g.
template<typename T1, typename ...T, typename std::enable_if_t<!std::is_same_v<std::decay_t<T1>, vec>>* = nullptr>
vec(T1&& t, T&&... args) : d{std::forward<T1>(t), std::forward<T>(args)...}{}
As #NathanOliver suggested you might need to define the default constructor if you use the constructor template as the default constructor.
Overload resolution is tricky. A perfect match will be chosen before one that requires a conversion.
This template constructor is a perfect match for anything:
template<typename ...T> vec(T&&... args);
This copy constructor is a perfect match for const vec types, and in that case is a better match than the template (because all things equal, a non-template function is defined to be a better match.)
vec(vec const& other); // copy constructor
In your code, the copy constructor is implicitly declared by the compiler.
Now, when you do this:
vec<3> test{5.f, 10.f, 3.f};
asdf(test); // error
The problem is that you are creating a non-const "test" object, so overload resolution finds a perfect match with the templated constructor, but must do a "const conversion" to match the copy constructor (and the copy constructor is what you want to use.) Therefore, it selects the template function and fails.
However, if you declare test as a const object, it will compile and work as you probably expect:
vec<3> const test{5.f, 10.f, 3.f}; // **** Notice, const now
asdf(test); // ok
What you really want to do is prevent the template constructor from dominating the copy constructor, even for const conversions, and that can be done in multiple ways.
add constraints to the constructor to prevent it from matching vec objects
overload the copy constructor with const and non-const versions
In c++20 the first approach is straight-forward. Just add a requires clause to ensure that none of your arguments are of type vec:
template<typename ...T>
vec(T&&... args) requires (not (std::is_same_v<T, vec> && ...))
: d{args...}{}
std::enable_if is another approach on older compilers or langauge levels.
Another way you can accomplish it is to have "two" copy constructors:
vec(vec const&) = default;
vec(vec & other) = default;
This covers both cases of const and non-const vec arguments. Being non-templates, when passing a vec, one of them will match better than the template and every other type will still select the template.
It will still fail if you mix different sized vecs, though, but you probably don't want that anyway. But if you do, you can add another template specifically for it:
// Only used when Size does not match "our size"
template<auto Size>
vec(vec<Size> const & other) {
}
Your issue here is that your
template<typename ...T>
vec(T&&... args) : d{args...}{}
is greedy and will be used in place of the built in copy constructor since T&& will resolve to a better match. To fix this, you just need to add a copy constructor that is a better match then your template, and you can do that by adding
vec(vec&) = default; // for lvalues
vec(const vec&) = default; // for const lvalues
vec(vec&&) = default; // for rvalues
to you class which you can see working in this live example.

return a constructible from a variant being constructed from an alternative

Is it okay to have a struct that is constructible from a variant (say, from std::variant), and to return such structure implicitly constructed from one of the variant's alternatives?
Consider the following code:
#include <utility>
struct type1_t
{
};
struct type2_t
{
};
struct variant
{
/*template <typename T>
variant(T&& t)
{}*/
variant(type1_t){}
};
struct var_holder_t
{
var_holder_t(variant v)
: m_var(std::move(v))
{}
private:
variant m_var;
};
var_holder_t foo()
{
return type1_t{ }; // <====== offending line
}
MSVC 2017, 2019 allow that, but GCC and clang does not compile. Changing to the other variant's constructor or even using boost::variant or std::variant doesn't help.
Worth noting that changing the offending line to return {type1_t{ }}; makes it compile somehow. Currently I'm lost, though technically speaking adding two extra {} doesn't do much harm readability-wise, probably I'll stick to that at the moment, just wish for an answer.
Also making var_holder_t's constructor template (forwarding) helps, and it makes var_holder_t constructible from type1_t directly (and very likely a lot better performance-wise), but at the moment I want to keep var_holder_t completely non-template - it's supposed (thought, designed) to be casually-written simple code.
Update: what actually confused me is that Clang emits this message:
note: candidate constructor not viable: no known conversion from 'type1_t' to 'variant' for 1st argument var_holder_t(variant v)
Which is weird, because clearly there is a conversion from type1_t to variant, but looks like its just bogus diagnostics. To put it together, I think I can come up with a simple reason why the construction above does not work: it requires two implicit conversions, but the rules are that only one is considered.
The below statement works because list initialization is being performed.
return {type1_t{ }};
copy-list-initialization
return { arg1, arg2, ... } ; (8)
List initialization is performed in the following situations:
...
direct-list-initialization (both explicit and non-explicit constructors are considered)
...
8) in a return statement with braced-init-list used as the return expression and list-initialization initializes the returned object
Here var_holder_t is initialized with a type1_t object and it works as expected.

class constructor precedence with a variadic template constructor for a value wrapper

Today I've discovered that I don't understand the C++ constructor precedence rules.
Please, see the following template struct wrapper
template <typename T>
struct wrapper
{
T value;
wrapper (T const & v0) : value{v0}
{ std::cout << "value copy constructor" << std::endl; }
wrapper (T && v0) : value{std::move(v0)}
{ std::cout << "value move constructor" << std::endl; }
template <typename ... As>
wrapper (As && ... as) : value(std::forward<As>(as)...)
{ std::cout << "emplace constructor" << std::endl; }
wrapper (wrapper const & w0) : value{w0.value}
{ std::cout << "copy constructor" << std::endl; }
wrapper (wrapper && w0) : value{std::move(w0.value)}
{ std::cout << "move constructor" << std::endl; }
};
It's a simple template value wrapper with copy constructor (wrapper const &), a move constructor (wrapper && w0), a sort of value copy constructor (T const & v0), a sort of move constructor (T && v0) and a sort of template construct-in-place-the-value constructor (As && ... as, following the example of emplace methods for STL containers).
My intention was to use the copy or moving constructor calling with a wrapper, the value copy or move constructor passing a T object and the template emplace constructor calling with a list of values able to construct an object of type T.
But I don't obtain what I expected.
From the following code
std::string s0 {"a"};
wrapper<std::string> w0{s0}; // emplace constructor (?)
wrapper<std::string> w1{std::move(s0)}; // value move constructor
wrapper<std::string> w2{1u, 'b'}; // emplace constructor
//wrapper<std::string> w3{w0}; // compilation error (?)
wrapper<std::string> w4{std::move(w0)}; // move constructor
The w1, w2 and w4 values are constructed with value move constructor, emplace constructor and move constructor (respectively) as expected.
But w0 is constructed with emplace constructor (I was expecting value copy constructor) and w3 isn't constructed at all (compilation error) because the emplace constructor is preferred but ins't a std::string constructor that accept a wrapper<std::string> value.
First question: what am I doing wrong?
I suppose that the w0 problem it's because s0 isn't a const value, so the T const & isn't an exact match.
Indeed, if I write
std::string const s1 {"a"};
wrapper<std::string> w0{s1};
I get the value copy constructor called
Second question: what I have to do to obtain what I want?
So what I have to do to make the value copy constructor (T const &) to get the precedence over the emplace constructor (As && ...) also with not constant T values and, mostly, what I have to do to get the copy constructor (wrapper const &) take the precedence constructing w3?
There is no such thing as "constructor precedence rules," there's nothing special about constructors in terms of precedence.
The two problem cases have the same underlying rule explaining them:
wrapper<std::string> w0{s0}; // emplace constructor (?)
wrapper<std::string> w3{w0}; // compilation error (?)
For w0, we have two candidates: the value copy constructor (which takes a std::string const&) and the emplace constructor (which takes a std::string&). The latter is a better match because its reference is less cv-qualified than the value copy constructor's reference (specifically [over.ics.rank]/3). A shorter version of this is:
template <typename T> void foo(T&&); // #1
void foo(int const&); // #2
int i;
foo(i); // calls #1
Similarly, for w3, we have two candidates: the emplace constructor (which takes a wrapper&) and the copy constructor (which takes a wrapper const&). Again, because of the same rule, the emplace constructor is preferred. This leads to a compile error because value isn't actually constructible from a wrapper<std::string>.
This is why you have to be careful with forwarding references and constrain your function templates! This is Item 26 ("Avoid overloading on universal references") and Item 27 ("Familiarize yourself with alternatives to overloading on universal references") in Effective Modern C++. Bare minimum would be:
template <typename... As,
std::enable_if_t<std::is_constructible<T, As...>::value, int> = 0>
wrapper(As&&...);
This allows w3 because now there is only one candidate. The fact that w0 emplaces instead of copies shouldn't matter, the end result is the same. Really, the value copy constructor doesn't really accomplish anything anyway - you should just remove it.
I would recommend this set of constructors:
wrapper() = default;
wrapper(wrapper const&) = default;
wrapper(wrapper&&) = default;
// if you really want emplace, this way
template <typename A=T, typename... Args,
std::enable_if_t<
std::is_constructible<T, A, As...>::value &&
!std::is_same<std::decay_t<A>, wrapper>::value
, int> = 0>
wrapper(A&& a0, Args&&... args)
: value(std::forward<A>(a0), std::forward<Args>(args)...)
{ }
// otherwise, just take the sink
wrapper(T v)
: value(std::move(v))
{ }
That gets the job done with minimal fuss and confusion. Note that the emplace and sink constructors are mutually exclusive, use exactly one of them.
As OP suggested, putting my comment as an answer with some elaboration.
Due to the way overload resolution is performed and types are matched, a variadic forward-reference type of constructor will often be selected as a best match. This would happen because all const qualification will be resolved correctly and form a perfect match - for example, when binding a const reference to a non-const lvalue and such - like in your example.
One way to deal with them would be to disable (through various sfinae methods at our disposal) variadic constructor when argument list matches (albeit imperfectly) to any of other available constructors, but this is very tedious, and requires ongoing support whenever extra constructors are added.
I personally prefer a tag-based approach and use a tag type as a first argument to variadic constructor. While any tag structure would work, I tend to (lazily) steal a type from C++17 - std::in_place. The code now becomes:
template<class... ARGS>
Constructor(std::in_place_t, ARGS&&... args)
And than called as
Constructor ctr(std::in_place, /* arguments */);
Since in my experience in the calling place the nature of constructor is always known - i.e. you will always know if you intend to call forward-reference accepting constructor or not - this solution works well for me.
As said in the comment, the problem is that the variadic template constructor
takes argument by forwarding reference, so it is a better match for non const lvalue copy or const rvalue copy.
There are many way to disable it, one efficient way is to always use a tag as in_place_t as proposed by SergeyA in its answer. The other is to disable the template constructor when it matches the signature of a copy constructor as it is proposed in the famous Effective C++ books.
In this case, I prefer to declare all possible signature for copy/move constructors (and also copy/move assignment). This way, whatever new constructor I add to the class, I will not have to think about avoiding copy construction, it is short 2 line of code, easy to read and it does not pollute the interface of other constructors:
template <typename T>
struct wrapper
{
//...
wrapper (wrapper& w0) : wrapper(as_const(w0)){}
wrapper (const wrapper && w0) : wrapper(w0){}
};
NB: this solution should not be used if one plan to use it as a volatile type, or if all the following condition are fullfilled:
the object size is smaller than 16bytes (or 8 byte for MSVC ABI),
all member suboject are trivially copyable,
this wrapper is going to be passed to functions where special care is taken for the case where the argument is of a trivially copyable type and its size is lower than the previous threshold because in this case, the argument can be passed in a register (or two) by passing the argument by value!
If all these requirement are fulfilled, then you may think about implementing less maintainable (error prone -> next time one will modify the code) or client interface polluting solution!

std::tuple with generic types like boost::any

Dear fellow programmers,
the code below gives me some headaches. It tries to add a 'generic' object (=object that can be constructed from anything) to a tuple and then copy that tuple.
#include <tuple>
#include <iostream>
#include <typeinfo>
struct anything
{
anything()
{}
anything(const anything&)
{
std::cout << "Called copy c'tor" << std::endl;
}
template<class T>
anything(T arg)
{
std::cout << "Called c'tor with with argument of type " << typeid(arg).name() << std::endl;
// new T(arg); // causes stack overflow
}
};
int main()
{
std::tuple<anything> t;
//
std::cout << "Copy constructing t2, expecting copy c'tor to be called." << std::endl;
std::tuple<anything> t2(t);
return 0;
}
With VS 2015 Update 2 it doesn't even compile, the line
std::tuple<anything> t2(t);
triggers a compiler error deep in tuple.h.
With gcc 5.3.1 it compiles, but the output is not what I'd expect:
Copy constructing t2, expecting copy c'tor to be called.
Called copy c'tor
Called c'tor with with argument of type St5tupleIJ8anythingEE
What I don't understand is the last line, i.e. why the templated constructor gets called with std::tuple as argument?
This is actually a real world problem. In my application I use a boost::signal of signature
typedef boost::type_erasure::any
<boost::mpl::vector<
boost::type_erasure::typeid_<>,
boost::type_erasure::copy_constructible<>,
boost::type_erasure::less_than_comparable<>,
boost::type_erasure::equality_comparable<>,
boost::type_erasure::relaxed
>> KeyType;
boost::signals2::signal<void(const KeyType&)>
Boost signals internally wraps the argument in a tuple before calling the slot function with it, which eventually results in a stack overflow, because the tuple c'tor calls the any c'tor with tuple as argument and the any c'tor then calls the tuple c'tor with 'any of tuple' and so on and on and on...
Let's go through overload resolution for:
std::tuple<anything> t2(t);
We have three viable constructors at our disposal:
explicit tuple( const Types&... args ); // (2) with Types = [anything]
template< class... UTypes >
explicit tuple( UTypes&&... args ); // (3) with UTypes = [std::tuple<anything>&]
tuple( const tuple& other ) = default; // (8)
Which have these argument lists:
tuple(const anything& ); // (2)
tuple(std::tuple<anything>& ); // (3)
tuple(std::tuple<anything> const& ); // (8)
(2) involves a user-defined conversion whereas (3) and (8) are exact matches. When it comes to reference bindings:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if [...] S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same
type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers
is more cv-qualified than the type to which the reference initialized by S1 refers.
So (3) is preferred - since it's less cv-qualified than (8). That constructor calls anything(std::tuple<anything> ).
As far as solutions, what you need is for (3) to not be considered in this case - we need to make it not a viable option (since (8) is already preferred to (2)). Currently, the easiest thing is just to make your constructor explicit:
template<class T>
explicit anything(T arg) { ... }
this works since (3) is specified in terms of is_convertible<>, which will return false on explicit conversions. However, that's currently considered a defect and will likely be changed in the future - since after all, we are explicitly constructing every aspect here so the explicit constructors should still be considered!
Once that happens, you're kind of out of luck as far as direct copy construction goes. You'd have to do something like disable your anything constructor for tuple? That seems... not great. But in that case, marking that constructor explicit would still work for copy-initialization:
std::tuple<anything> t2 = t;
which works now even without marking the anything constructor explicit, due to the same aforementioned defect.
If you look at the implementation of the tuple you'll notice that
_Tuple_val<_This> _Myfirst; // the stored element
...
template<class _This2,
class... _Rest2,
class = typename _Tuple_enable<tuple<_This2, _Rest2...>, _Myt>::type>
explicit tuple(_This2&& _This_arg, _Rest2&&... _Rest_arg)
: _Mybase(_STD forward<_Rest2>(_Rest_arg)...),
_Myfirst(_STD forward<_This2>(_This_arg))
{ // construct from one or more moved elements
}
The constructor of the tuple passes first argument to the the constructor of the first element of tuple. As variable t has type std::tuple<anything> compiler finds the best match for constructing anything with t - a template constructor.
To copy a tuple you simply need to write
std::tuple<anything> t2 = t;
UPD.
According to standard std::tuple provides following constructors:
template <class... UTypes>
explicit constexpr tuple(const Types&...);
template <class... UTypes>
constexpr tuple(const tuple<UTypes...>&);
And apparently your template constructor is a better match than copy constructor of the tuple.

std::is_assignable<A, B>::value always true when A has variadic constructor

Hopefully the problem is obvious from the question :)
If not, consider the following code:
template <typename Ty>
class test
{
public:
test(std::string &v) : val(v) {}
template <typename... Args>
test(Args&&... args) : val(std::forward<Args>(args)...) {}
Ty val;
};
int main(void)
{
std::cout << std::is_assignable<test<std::string>, std::string>::value << std::endl;
std::cout << std::is_assignable<test<std::string>, std::vector<int>>::value << std::endl;
}
The output is true in both cases, tested using Visual Studio 2013 update 3 as well as ideone. This just seems wrong to me on many different levels. For one, attempting to construct/assign std::vector<int> to an instance of the type test<std::string> will not compile (obviously). I can live with this as the compiler is likely just checking types and whether or not an acceptable function exists (and not whether or not said function compiles).
What bugs me the most is that constructors should have no effect on assignment. This seems to be true (at least in GCC) when removing the variadic constructor. The output is then false in both cases. On Visual Studio, the output is still true for the first line of output; I'll file a bug on that tomorrow.
Anyway, is this correct behavior? It seems quite counter-intuitive.
This doesn't have much to do with variadic constructor templates. The problem is that you have a constructor template taking an unconstrained universal reference parameter. Using the following constructor also prints 1 in both cases:
template <typename Args>
test(Args&& args) : val(std::forward<Args>(args)) {}
The definition of is_assignable is (§20.10.4.3 [meta.unary.prop], Table 49):
The expression declval<T>() = declval<U>() is well-formed when
treated as an unevaluated operand (Clause 5). Access checking is
performed as if in a context unrelated to T and U. Only the
validity of the immediate context of the assignment expression is
considered. [ Note: The compilation of the expression can result in
side effects such as the instantiation of class template
specializations and function template specializations, the generation
of implicitly-defined functions, and so on. Such side effects are not
in the “immediate context” and can result in the program being
ill-formed. —end note ]
This allows implicit conversion sequences to be used. Universal references can bind to everything, so your variadic constructor defines implicit conversions from everything to test. (Your constructor taking a std::string & doesn't have an effect on is_assignable because the return type of declval<std::string>() is an rvalue reference and so doesn't bind to non-const lvalue references.)
If you don't want this to happen, mark your constructor explicit. It's probably also a good idea to constrain your variadic template constructor using std::is_constructible:
template <typename... Args, typename = typename std::enable_if<std::is_constructible<Ty, Args...>::value>::type>
explicit test(Args&&... args) : val(std::forward<Args>(args)...) {}
Constructors affect assignment via the implicit copy assignment operator:
test& operator=(test const&) = default;
The effect is:
test<std::string> a;
std::string b;
std::move(a) = std::move(b);
// a.operator=(b) constructs a temporary test<std::string> from b
If you delete the copy assignment operator, then is_assignable will become false in both cases.
An alternative is to mark your converting constructors explicit; this will prevent them being available to convert the RHS operand of the assignment operator.
To correct the determination of the compiler on your variadic template constructor, you can use decltype SFINAE:
template <typename... Args,
typename = decltype(Ty(std::forward<Args>(std::declval<Args>())...))>
test(Args&&... args) : val(std::forward<Args>(args)...) {}
The difference in behaviour between gcc and Visual Studio is a non-standard extension on the part of the latter compiler: rvalue to lvalue conversion Visual Studio. This allows the lvalue reference in the std::string& constructor to bind to the xvalue std::string returned from std::declval<std::string> in the definition of is_assignable. gcc will give the same result if you pass std::string& to is_assignable or alternatively if you make that constructor take a const lvalue reference or a rvalue reference.