Dans cet exemple, nous pouvons citer la norme:
12.2 les objets Temporaires [classe.temporaire]
Objets temporaires sont détruits comme la dernière étape dans l'évaluation de la pleine expression de (1.9) que (lexicalement) contient le point où ils ont été créés. Cela est vrai même si cette évaluation se termine en levant une exception. La valeur des calculs et des effets secondaires de la destruction d'un objet temporaire sont associées avec le plein d'expression, non à une sous-expression.
C'est après le point-virgule de votre ligne:
const char * s = (s1+s2).c_str(); // <- Here
Donc ici:
printf("%s\n",s); // This line will now cause undefined behaviour.
Pourquoi? Parce que votre objet est détruit, on ne sait plus ce qui est à cet endroit aujourd'hui...
La mauvaise chose est que, avec un comportement indéterminé, votre programme peut sembler à première fois, mais... Il va se planter pour assurer au pire moment...
Vous pouvez le faire:
printf( "%s\n", (s1+s2).c_str() );
Il va travailler parce que l'objet n'est pas détruite, mais (n'oubliez pas, après le point-virgule...).